povídky foto kultura ostatní stripy
facebook twitter
ASCII blog doomsday party

k47.cz

23. 3. 2012

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:


publikováno 23. 3. 2012

příbuzné články:
Scala - líné parametry, líné závislosti a líné proxy
Go třídy ve Scale
Scala - metody ála Smalltalk
Scala - klasický for cyklus
Scala - dynamický jazyk

sem odkazují:
Prog smetí