funkcionálně.cz

Přední český blog o funkcionálním programování, kde se o funkcionálním programování nepíše
««« »»»

Fluent interface a funkcionální programování

24. 12. 2012 — k47

Fluent interface (FI) je maličký OOP návrhový vzor pro návrh příjemnějších API. Metoda objektu je fluent, když poté, co provede svojí práci, vrátí objekt samotný. Takto je možné jednotlivá volání plynule řetězit.

Typická ne-fluent metoda vrací void/unit/nic (doufám, že mi prominete, že jsem pro ukázky zvolil programovací jazyk Scala):

def addEntry(e: Entry): Unit =
  this.entries.add(e)

Když pak takovou metodu zavoláme několikrát v řadě, nemáme jinou šanci než to udělat takto:

myObject.addEntry(e1)
myObject.addEntry(e2)
myObject.addEntry(e3)

Fluent metoada může být definována například následujícím způsobem:

def addEntry(e: Entry): MyObject = {
  this.entries.add(e) // side-effect
  this                // return this => fluent
}

Použití je pak o něco příjemnější:

myObject.addEntry(e1).addEntry(e2).addEntry(e3)

Tolik k principům, které jsou stejně všem dobře známé. Teď bych se ale chtěl podívat, jak je na tom FI ve vztahu k funkcionálnímu programování.

Funkcionální kód na první pohled vypadá, že je vždycky a za všech okolností fluent, ale jde jenom o povrchní podobnost. FI je totiž nevyhnutelně spjato s vedlejšími účinky, kterým se funkcionální kód snaží v co největší míře vyhýbat. Každá FI metoda provede nějaké modifikace this objektu a pak ho ve snaze zpohodlnit API vrátí. Naproti tomu (čistě) funkcionální kód vždycky vrátí zcela novou kopii objektu, protože nic jiného ani nemůže udělat, nemůže nijak jinak komunikovat s okolím.

Ale to neznamená, že je FI ve své podstatě špatné. Měnitelná povaha FI nemusí být na škodu, když je použita jako builder, jehož všechny mutace jsou lokální a objekt je po vytvoření zafixován a už se nemění (objekt, metoda nebo funkce může používat divoké změny stavů a vedlejší efekty, ale když vnitřní dovpčinu nikdy nemůžeme pozorovat zvenčí, jde fakticky také o funkcionální kód).


V kódu, který pracuje se neměnnými (immutable) objekty, dostaneme FI většinou zadarmo. Ve Scale je to triviální:

def withEntry(e: Entry) =
  copy(entries = entries :+ e)

Ale naopak je to složitější (FI kódu přidat funkcionální povahu)

def withEntry(e: Entry) = {
  val copy = this.clone
  copy.entries.add(e)
  copy
}


I když můžeme pomocí FI zřetězovat libovolné operace, ne vždycky to dává smysl. Pokud mají jednotlivé metody sémantiku výrazů (expression), je FI naprosto ideální. Když mají sémantiku příkazů (statement), hodí se už podstatně méně.


Jedna malé poznámka na okraj: Ve Scale můžete zaručit, že metoda vrátí stejnou instanci (nejenom typ) tím, že nastavíte návratový typ na this.type.

trait A {
  def getA: A             // vrací nějaký objekt typu A
  def getThisA: this.type // vrací this, kontrolováno kompilátorem
}
@kaja47, kaja47@k47.cz, deadbeef.k47.cz, starší články