Změna chování {default} v Latte – sjednocení s ??= operátorem
- David Grudl
- Nette Core | 8227
Rád bych otevřel diskusi ohledně možné změny chování značky
{default}
v Latte pro budoucí verze (např. 3.1).
- Značka
{default $var = 123}
vytvoří proměnnou, pokud neexistuje. - Pokud
$var = null
, značka{default}
ponechá hodnotu null. - Toto chování vzniklo dávno před zavedením PHP operátoru
??=
. - PHP operátor
??=
(nullsafe assignment operator) přepíše hodnotu null.
Zvažuji, zda by nebylo vhodné sjednotit chování {default}
s PHP operátorem ??=
. To by znamenalo, že by
{default}
přepisovalo i null hodnoty.
Myslíte, že by tato změna byla přínosná? Bylo by vhodné udělat toto chování konfigurovatelné? Pokud ano, jak by mohl vypadat způsob konfigurace?
Díky za názory a podněty.
- mystik
- Člen | 312
Měnilo se nějak chování {default}
v nové verzi Latte?
Začaly nám totiž padat testy u efabrica/phpstan-latte
při
analýze šablon s tímto tagem.
Šablona obsahuje:
{default $someVariableWithDefault = 'default value'}
Proměnná $someVariableWithDefault
není definovaná (což
analyzátor ví).
Dřív to PHPStan vyhodnotil že tak, že typ proměnné je
'default value'
, ale teď to
dává 'default value'|null
Je to záměrná změna chování nebo chyba v analýze?
Vygenerovaný kód v šabloně vypadá teď takto:
$someVariableWithDefault ??= \array_key_exists('someVariableWithDefault', \get_defined_vars()) ? \null : 'default value';
Z toho mi vychází že pokud proměnná není definovaná mělo by se
přiřadit 'default value'
, ale PHPStan možná nedává tu
„magii“ s array_key_exists
a get_defined_vars
.
Editoval mystik (31. 10. 13:26)
- David Grudl
- Nette Core | 8227
A co by mělo být správné chování? Typ proměnné můžu být vlastně cokoliv, včetně null.
- m.brecher
- Generous Backer | 871
V diskusi zazněly názory, že je trend používat v PHP null nikoliv ve smyslu ‚undefined‘, ale spíše jako plnohodnotnou ‚default‘ hodnotu:
@mystik
Pokud explicitne predam do parameteu null cekal bych ze zustane zachovane null .....
@hrach
PHP zavedlo null jako plnohodnotny „typ“, tzn. povazovat null hodnotu za „undefined variable“, pro ktery potrebuji default, mi prijde neprakticke a „mirici“ opacnym smerem.
S tímto názorem bych nesouhlasil. Napříč jazyky včetně PHP je primárním významem null ‚undefined‘ a tak by se null mělo v PHP používat. Rozdíl mezi ‚undefined‘ a null by měl být jenom ten, že hodnotou null vědomě deklaruji že hodnotu neznám a nechci vyhazovat výjimku. Tag {default} ale není tak důležitý a podpora pro změnu v komunitě nulová, takže z pragmatického pohledu nemá asi cenu změnu dělat.
Editoval m.brecher (1. 11. 19:38)
- m.brecher
- Generous Backer | 871
@mystik
Tamto se to chova u defaultu parametru funkce. IMHO by se to melo stejne chovat u defaultu parametru sablony.
Máš pravdu, je to docela silný argument proti změnám. PHP funkce se opravdu chová, jako kdyby null nemělo význam ‚undefined‘, ale jako kdyby šlo o nějakou hodnotu.
Pojďme se podívat na jiné části PHP / Nette Frameworku, jak rozlišují mezi null a ‚undefined‘:
nullsafe operátory PHP
$var; // undefined
bdump($var ?? 'default'); // 'default'
$var = null;
bdump($var ?? 'default'); // 'default'
$var; // undefined
$var ??= 'default';
bdump($var); // 'default'
$var = null;
$var ??= 'default';
bdump($var); // 'default'
}
presentery nette, parametr var je ‚undefined‘
public function actionDefault(?string $var): void
{
bdump($var); // null
}
public function actionDefault(?string $var = 'default'): void
{
bdump($var); // 'default'
}
Nullsafe operátory PHP nerozlišují mezi null a ‚undefined‘, akce presenterů Nette předá do neexistujícího parametru hodnotu null, kterou lze přepsat defaultní hodnotou. Tyto prvky tedy mezi ‚undefined‘ a null nerozlišují.
Tag {default} byl navržen v době, kdy moderní nullsafe operátory neexistovaly. PHP se ale vyvíjí směrem k modernímu jazyku a nově přidávané prvky už respektují všeobecně uznávané normy a pravidla běžné v moderních jazycích. Nette bylo vždy o krok před PHP a snažilo se staré nepovedené koncepty nějak opravit.
PHP nemá defaultní hodnotu parametru funkce správně navrženu, ale nullsafe operátory už správně navrženy jsou. Nette by si mělo v PHP vzít inspiraci z těch moderních dobře navržených koncepcí, nikoliv z těch starých, nepovedených, které tam zůstávají kvůli BC.
Samozřejmě změny jsou nepříjemné a vyžadují přepsání určitého počtu šablon. Pokud by se nové chování dalo konfigurací vypnout, tak by se přece změny nemusely nikoho dotknout ??
- hrach
- Člen | 1838
@mbrecher Podivej se na to spis takto:
- nullsafe operator je neco jineho nez default hodnota a navic uz jde v latte pouzit
- predstava, ze mi nekdo prepisuje explicitne nastaveny null me desi, ikdyz chapu, ze tu zadna detekce na explicitne nastaveny null nemuze byt;
- default chapu jako vhodny konstrukt, kdyz je sablona pouzita v ruznych flow a nekde proste tu variable nenastavim
Obecne bych nedelal zbytecny bc breaky. Pokud potrebujeme “replacovat”
null a chceme to mit jako macro, zavedl bych nove
makro. {defaultIfNull $var = 123}
- Kamil Valenta
- Člen | 820
Souhlas s @hrach
Nemyslím si, že nerozlišování null a undefined je napříč jazyky. Třeba
Python a JavaScript je rozlišují. A skutečně, pokud někde v argumentu
uvedu null, očekávám tam null, ne cokoliv jiného, co je výchozí hodnotou.
Výchozí hodnotu čekám jen v případě undefined.
Naopak bych řekl, že ta vágnost při vyhodnocování v PHP je spíš obtíž, každého asi někdy potrápila empty()…
Než ale zakládat novou variantu makra defaultIfNull, rozšířil bych
podporu pro {default $var ??= 123}, jak navrhoval @Mistik
I když to jde přes makro do, připadá mi intuitivní mít k dispozici
{default $var = 123} a {default $var ??= 123}, rozdíl je na první pohled
jasný, člověk si nemusí pamatovat nic nového a řeší situaci vždy
stejným makrem.
Editoval Kamil Valenta (5. 11. 12:49)
- m.brecher
- Generous Backer | 871
@hrach
predstava, ze mi nekdo prepisuje explicitne nastaveny null me desi
@KamilValenta
A skutečně, pokud někde v argumentu uvedu null, očekávám tam null, ne cokoliv jiného, co je výchozí hodnotou.
Explicitní nastavení hodnoty null, která se nesmí přepsat defaultní hodnotou, zatímco ‚undefined‘ se přepsat musí, signalizuje použití null s jiným významem než ‚undefined‘. Dělá se to běžně, ale vede to k méně čitelnému kódu a vznikají komplikace. Komponenta předá do šablony null s tím, že se null nesmí přepsat defaultní hodnotou. Autor šablony ale používá null stejně jako ‚undefined‘ a místo {default $num = 0} použije {do $num ??= 0}. Autor komponenty nemůže zabránit tomu, aby autor šablony nepřepsal null defaultní hodnotou. I když funkci {default} nebudeme měnit může nastat tato „děsivá“ varianta :).
Jak se „děsivé“ variantě vyhnout? Ideálně tak, že všichni budeme používat null ve významu ‚undefined‘. Jestliže vznikne situace, že přepsání null defaultní hodnotou způsobí problém, použijme místo null jinou hodnotu (0, false) a ponechejme null volné pro možnost explicitní deklarace ‚undefined‘.
Souhlasím ale s @KamilValenta, @mystik, @hrach, že ideální řešení je rozšířit syntaxi {default $var ??= 123}, a nedělat BC breaky, když to není nutné.
Editoval m.brecher (6. 11. 0:37)
- David Grudl
- Nette Core | 8227
Jsem nečekal, že taková blbost rozproudí takovou diskusi :-) Otázka spíš mířila na to, co by vám čistě prakticky víc vyhovovalo. Sám jsem se spálil a při pohledu na kód:
{default $lang = cs}
<html lang={$lang}>
…si neuvědomil, že $lang
může být null
. Ale
holt to stačí jen vědět a jede se dál.
- Kamil Valenta
- Člen | 820
m.brecher napsal(a):
vede to k méně čitelnému kódu
To je subjektivní pohled na věc. Mystik uvedl příklad s foo(), který je naopak velmi čitelný a jiné chování by bylo matoucí.
Autor šablony ale používá null stejně jako ‚undefined‘ a místo {default $num = 0} použije {do $num ??= 0}. Autor komponenty nemůže zabránit tomu, aby autor šablony nepřepsal null defaultní hodnotou.
Autor šablony ví proč nerozlišuje (nebo naopak rozlišuje) null od
undefined. A autorovi komponenty to může být jedno, ten předal co chtěl a
šablona se s tím vypořádá podle svého.
Jedna šablona může být sdílena více komponentami, ne každá komponenta do
ní pošle stejné atributy (undefined), ale když už je pošle, mohou být
prázdné (null).
Jestliže vznikne situace, že přepsání null defaultní hodnotou způsobí problém, použijme místo null jinou hodnotu (0, false) a ponechejme null volné pro možnost explicitní deklarace ‚undefined‘.
Rozumím tomu dobře, že zavrhuješ používání null ve významu null a undefined ve významu undefined – místo toho chceš používat null a undefined ve významu undefined a 0+false ve významu null? Opravdu Ti to dává smysl?
- m.brecher
- Generous Backer | 871
@mystik
Rozliseni null vs undefined se bezne pouziva v rade jazyku. Proc myslis, ze by se to rozlisovat nemelo?
Null by se mělo používat ve významu ‚hodnota neexistuje‘, což je sice stejný význam jako u neinicializované hodnoty ale null a ‚undefined‘ se podstatně liší tím, zda tento stav znamená chybu nebo ne. Null je vědomé nastavení neexistující hodnoty, které v kódu zpracujeme bez výjimky, zatímco neinicializovaná proměnná znamená chybu, která by se měla v kódu opravit. Všichni to takto běžně používáme.
Jenže v aplikacích se často null ve významu ‚neexistující hodnota‘ nepoužívá. Běžně se návrh databáze příliš zjednodušuje a hodnota null pak něco konkrétního znamená, obvykle nahrazuje jedno boolean pole. Příklad:
Striktní přístup – null ve významu ‚hodnota neexistuje‘
public bool $runProcess; // true = run process, false = do not run process
public ?DateTime $startAt; // DateTime = datum spuštění, null = datum neexistuje
Zjednodušený přístup – null se skrytým významem ‚do not run process‘
public ?DateTime $startRunAt; // DateTime = run process + datum spuštění, null = do not run process + datum neexistuje
Ve druhém případě $startRunAt = null má skrytý význam ‚do not run process‘ zatímco ‚undefined‘ nikoliv. V takovém případě se explicitně nastavené null nesmí přepisovat nějakou defaultní hodnotou – to je případ který Jsi zmínil. V prvním případě to nevadí. A protože bezpochyby je druhý způsob použití null běžný, tak je návrh neměnit {default}, rozumný a každý si použije v aplikaci tu variantu, která mu vyhovuje.
- David Grudl
- Nette Core | 8227
@mystik
Rozlišování null vs undefined se právě moc neosvědčilo. Protože to zbytečně zvyšuje kognitivní zátěž jazyka.
Peklo je to v JavaScriptu, který má různé typy null
a
undefined
. Například TypeScript coding standards přímo
doporučují používat pouze undefined
. Ten je v JS přirozený:
neexistující proměnná je undefined
, funkce bez return vrací
undefined
, nepředaný parametr funkce je undefined
atd. Naopak null
je v JS objekt 🤦♂️ Komplikací je, že
některé funkce, např. z DOM, vrací historicky null
.
V PHP je to podobné, jen jazykově nativní je tady null
, tj.
neexistující proměnná je null
, funkce bez return vrací
null
, atd.
- David Grudl
- Nette Core | 8227
Jinak v souvislosti ve šablonama je ještě důležité je tu ještě jedna pikoška. Pokud si kvůli kontrole a napovídání typů vytvořím třídu:
class TemplateParameters
{
public string $lang;
}
$params = new TemplateParameters;
//$params->lang = 'cs';
$html = $latte->render('template.latte', $params);
…tak pokud nenastavím $params->lang
, bude mi fungovat
{default}
v šabloně. Ale pozor, pokud by property
$lang
neměla typ:
class TemplateParameters
{
public $lang;
}
…tak už by {default}
nefungoval, protože netypované
proměnné mají vždy výchozí hodnotu null
, kterou už pak
{default}
nepřebije.
- Kamil Valenta
- Člen | 820
David Grudl napsal(a):
Rozlišování null vs undefined se právě moc neosvědčilo. Protože to zbytečně zvyšuje kognitivní zátěž jazyka.
V jiných textech lze najít, že to naopak kognitivní zátěž snižuje. Bohužel, ať už člověk zastává libovolné paradigma, v obřím internetu najde zdroj, který mu jeho postoj potvrdí…
V PHP je to podobné, jen jazykově nativní je tady
null
, tj. neexistující proměnná jenull
, funkce bez return vracínull
, atd.
Úplně podobné to není, protože PHP zde opět trochu mlží svou
nejednoznačností. Neexistující proměnná není tak úplně
null
, jen se na null
překlápí při přístupu
k ní. Ano, další zmatek je, že není null jako null…
Struktura ZVAL totiž obsahuje atribut IS_NULL, který se používá pro
definované proměnné s hodnotou null
.
Undefined proměnná vůbec svůj ZVAL nemá, proto není tak úplně
null
. Ale při pokusu vypsat ji se za null
nahradí,
je to popsáno i v dokumentaci PHP.
Důvod, proč to tak obšírně píšu, je, že to vyjasňuje chování u default hodnot parametrů funkcí. Srovnej:
function foo(string $bar);
function foo(string $bar='default');
function foo(?string $bar);
function foo(?string $bar=null);
V 1. případě není bar undefined nikdy. V 2. být může a pak dostane hodnotu „default“ (null zde není ve hře vůbec), ve 3. případě není nikdy undefined, ale null být může. V posledním případě může být undefined a pak získá defaultní ZVAL IS NULL, nebo bude defined s hodnotou NULL nebo defined s explicitním stringem.
Lette macro {default}
se chová jako default parametr funkce.
Pokud se držíme toho, že latte je PHP a není jiný nový jazyk, tak je to
tak správně.