Scala - Booleovská kompozice funkcí
V projektu
Chanminer jsem měl nějaké funkce f, g,
h typu A => Boolean a potřeboval jsem je skládat
jako kdyby šlo o Boolean hodnoty, např: f && (g || h).
Bylo by pěkné, kdyby to šlo takhle přímo, ale bohužel FunctionN nemá
žádnou z metod &&, !!, unary_!
nebo ^.
Klasické řešení je vytvořit větší funkci, v níž zavolám
f, g a h a zkombinuji jejich
výsledky:
val x = { (a: A, b: B, c: C) => f(a, b, c) && (g(a, b, c) || h(a, b, c)) }
I když je to funkční řešení, je hned vidět jaké má nevýhody: musím několikrát opakovat všechny argumenty, což člověka začne štvát velice rychle. V ukázce jsou jména argumentů jenom jednopísmenné, kdyby měly popisný název, tak by se v záplavě písmen úplně ztratil záměr kódu.
Pomoci může ScalaZ a
monoidy, které se dají k podobnému účelu použít. Monoid je struktura s jednou
asociativní binární operací, nulovým prvkem a několika axiomy. Monoid je například
definován pro celá čísla, kde operace je + a nulový prvek je
0. Stejně tak je definován i pro funkce A => Boolean, kde
operace je logické nebo a nulový prvek je false (zjednodušeně
řečeno).
Takže když bych chtěl mezi sebou zorovat několik funkcí, můžu napsat:
val x = f |+| g |+| h
Jak vidno, není to příliš flexibilní řešení.
Stejně jako je na celých číslech definován další monoid, kde operace je násobení a nulový prvek je 1 existuje i na bool funkcích ještě jeden monoid pro and a true, ale aby se použil tenhle musely by naše predikáty vracet speciální typ BooleanConjunction a i tak není možné jednoduše míchat tyhle dva monoidy. Takže nic, tohle mi práci neulehčí.
ScalaZ ale nabízí další užitečnou věc: aplikativní buildery. Pomocí nich můžu naakumulovat několik hodnot/funkcí a na ně potom aplikovat funkci, která bere tolik parametrů, kolik je naakumulováno hodnot (zjednodušeně řečeno):
val x = (f |@| g |@| h) apply (_ && _ && _)
Problém je ale s aplikovanou složitějších funkcí. Když se v nich vyskytne závorka, Scala odvodí funkci s jiným počtem parametrů, než jsem zamýšlel, takže tohle nefunguje:
val x = (f |@| g |@| h) apply ((_ || _) && _) // compiletime error
Jde to takhle:
val x = (f |@| g |@| h) apply (_.||(_) && _)
Ale to si s sebou nese nepříjemně vysoký wtf faktor.
Tenhle nedostatek se dá vyřešit vypuštěním podtržítek z anonymní funkce a pojmenováním všech parametrů:
val x = (f |@| g |@| h) apply { (a, b, c) => (a || b) && c)
Výsledek funguje, ale je poněkud roztahaný.
Žádné z předchozích tří řešeních není ideální. Naštěstí si díky zázraku implicitní konverze můžu napsat několik jednoduchých kombinátorů, se kterými výsledný kód vypadá přesně jak jsem si představoval:




Jmenuji se Karel Čížex, v síti také známý jako 
komentáře