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 (před 7 lety) — k47

Fluent in­ter­face (FI) je ma­ličký OOP ná­vr­hový vzor pro návrh pří­jem­něj­ších API. Metoda ob­jektu je fluent, když poté, co pro­vede svojí práci, vrátí objekt sa­motný. Takto je možné jed­not­livá volání ply­nule ře­tě­zit.

Ty­pická ne-fluent metoda vrací void/unit/nic (doufám, že mi pro­mi­nete, že jsem pro ukázky zvolil pro­gra­mo­vací jazyk Scala):

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

Když pak ta­ko­vou metodu za­vo­láme ně­ko­li­krát v řadě, nemáme jinou šanci než to udělat takto:

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

Fluent me­to­ada může být de­fi­no­vána na­pří­klad ná­sle­du­jí­cím způ­so­bem:

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

Po­u­žití je pak o něco pří­jem­nější:

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

Tolik k prin­ci­pům, které jsou stejně všem dobře známé. Teď bych se ale chtěl po­dí­vat, jak je na tom FI ve vztahu k funk­ci­o­nál­nímu pro­gra­mo­vání.

Funk­ci­o­nální kód na první pohled vypadá, že je vždycky a za všech okol­ností fluent, ale jde jenom o po­vrchní po­dob­nost. FI je totiž ne­vy­hnu­telně spjato s ve­d­lej­šími účinky, kterým se funk­ci­o­nální kód snaží v co nej­větší míře vy­hý­bat. Každá FI metoda pro­vede nějaké mo­di­fi­kace this ob­jektu a pak ho ve snaze zpo­ho­dl­nit API vrátí. Na­proti tomu (čistě) funk­ci­o­nální kód vždycky vrátí zcela novou kopii ob­jektu, pro­tože nic jiného ani nemůže udělat, nemůže nijak jinak ko­mu­ni­ko­vat s okolím.

Ale to ne­zna­mená, že je FI ve své pod­statě špatné. Mě­ni­telná povaha FI nemusí být na škodu, když je po­u­žita jako bu­il­der, jehož všechny mutace jsou lo­kální a objekt je po vy­tvo­ření za­fi­xo­ván a už se nemění (objekt, metoda nebo funkce může po­u­ží­vat divoké změny stavů a ve­d­lejší efekty, ale když vnitřní do­vp­činu nikdy ne­mů­žeme po­zo­ro­vat zvenčí, jde fak­ticky také o funk­ci­o­nální kód).


V kódu, který pra­cuje se ne­měn­nými (im­mu­table) ob­jekty, do­sta­neme FI vět­ši­nou za­darmo. Ve Scale je to tri­vi­ální:

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

Ale naopak je to slo­ži­tější (FI kódu přidat funk­ci­o­nální povahu)

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


I když můžeme pomocí FI zře­tě­zo­vat li­bo­volné ope­race, ne vždycky to dává smysl. Pokud mají jed­not­livé metody sé­man­tiku výrazů (ex­pres­sion), je FI na­prosto ide­ální. Když mají sé­man­tiku pří­kazů (sta­te­ment), hodí se už pod­statně méně.


Jedna malé po­známka na okraj: Ve Scale můžete za­ru­čit, že metoda vrátí stej­nou in­stanci (neje­nom typ) tím, že na­sta­víte ná­vra­tový 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