Funkció összetétele rubinban

Ebben a cikkben a következő témákkal foglalkozunk:

  • a Funkciókompozíció használata a Ruby nyelvben
  • emberbarát szintaxis
  • hogyan működik a lambdákkal
  • Funkció összeállítás és Kacsa gépelés
  • Funkciókompozíció a színfalak mögött

Mielőtt elkezdené

Kérem, engedje meg, hogy itt bemutassam azt a platformot, amely segített elsajátítani a Rubyval kapcsolatos ismereteim nagy részét. Valóban, a Pluralsight egy csodálatos platform.

Az 50+ tanfolyam, amelyek különböző témákat fednek le a Ruby és Ruby on Rails témakörben, ez a legjobb módja annak, hogy tudását a következő szintre emelje!

Próbáld ki ingyen 👇😉



Köszönöm az idődet!

Funkció Összetétel

A CompSci-ben a Funkciókompozíció több függvény kombinálására utal bonyolultabbak létrehozása érdekében. A Rubyban ezt a fogalmat a Proc#<< és Proc#>> metódusok képviselik

increment = proc {|x| x + 1}
double    = proc {|x| x * 2}
(double << increment).call(5) # => 12

Lépésről lépésre részletezzük, mi történt az előző példában

increment = proc {|x| x + 1}
double    = proc {|x| x * 2}
x = 5
x = increment.call(x) # => 5 + 1 = 6
double.call(x)        # => 6 * 2 = 12

Mivel a bal-shift operátort használjuk, a jobb oldali proc (increment) kerül végrehajtásra először, és ennek a proc hívásnak az eredménye kerül átadásra a bal oldali proc hívás argumentumaként (double).

A mechanizmus ugyanaz a right-shift operátornál, kivéve, hogy a bal oldali proc végrehajtása először történik meg.

Emberbarát szintaxis

A Ruby által biztosított szintaxis emberbarát, és lehetőséget ad arra, hogy ugyanazt az eredményt többféleképpen biztosítsd. Ez a megközelítés lehetőséget ad arra, hogy közvetlenül a gondolataiból fejezze ki a megoldást. Valójában, míg más nyelveken egyedi szintaxisnak kell megfelelnie, és játszania kell a végrehajtási sorrenddel, hogy megfelelő eredményt érjen el a kompozícióhoz, a Rubyban nem kell megbirkóznia ezzel a kellemetlenséggel. Ezenkívül a szintaxis sokkal jobban olvasható Rubyval.

Hasonlítsuk össze a Funkciókompozíció használatát a Python és a Ruby-ban.

Pythonban

import functools
def double(x):
    return x * 2
def inc(x):
    return x + 1
def dec(x):
    return x - 1
def compose(*functions):
    return functools.reduce(lambda f, g: lambda x: f(g(x)), functions, lambda x: x)
func_comp = compose(dec, double, inc)
func_comp(10) # => 21

Rubyban

double = proc { |x| x * 2 }
inc    = proc { |x| x + 1 }
dec    = proc { |x| x - 1 }
(dec << double << inc).call(10) # => 21

Egyet kell értenie azzal, hogy a szintaxis sokkal természetesebb és olvashatóbb Rubyban. Ezenkívül a Pythonban a compose metódushívás argumentumaként átadott metódusok sorrendjével kell játszania, hogy biztosítsa a várt eredményt.

A Rubyban megváltoztathatja a végrehajtás sorrendjét, miközben ugyanazt az eredményt biztosítja anélkül, hogy kockáztatná a forráskód összezavarását.

(double << inc >> dec).call(10) # => 21
(dec << double << inc).call(10) # => 21
(inc >> double >> dec).call(10) # => 21

Funkció Összetétel lambdákkal

Először is nyugodtan tekintse meg a Proc vs Lambda in Ruby cikket, ha nem ismeri a két idióma közötti különbségeket.

Mivel a lambdas és a procs egyaránt a Proc osztály példányai, a Function Composition használható mindkét eszközzel.

double = proc   { |x| x * 2 }
inc    = lambda { |x| x + 1 }
dec    = proc   { |x| x - 1 }
(dec << double << inc).call(10) # => 21

Mivel a lambdák szigorúak az argumentumszámok tekintetében, óvatosnak kell lennie a mellékhatások elkerülése érdekében

double = proc   { |x| x * 2}
add    = lambda { |x, y| x + y }
(double << add).call(1)

termel

ArgumentError (wrong number of arguments (given 1, expected 2))

Tehát ennek javítására átadhatunk egy második argumentumot a call metódusnak

double = proc   { |x| x * 2}
add    = lambda { |x, y| x + y }
(double << add).call(1, 2) # => 6

Valójában itt a add lambda eredménye a double proc egyetlen argumentumaként kerül átadásra. Tehát (1 + 2) * 2 = 6.

A funkció összeállítása és a kacsagépelés

Tehát mi van, ha elmondom, hogy a Ruby Funkciókompozíciója nem kizárólag procokkal és lambdákkal működik?

class Triple
  def call(x)
    x * 3
  end
end
increment = ->(x) { x + 1 }
triple = Triple.new
(increment << triple).call(1) # => 4

Itt a triple a Triple példánya, amely a call metódusra válaszol. Valójában a Proc#<< és Proc#>> metódusok elvárják, hogy az argumentumként átadott objektum válaszoljon a call metódusra. Az egyetlen megkötés az, hogy az első függvénynek balról a Proc példányának vagy annak valamelyik szakterületének kell lennie.

Ez a megvalósítás a Duck Typing tervezési elv szabályait követi:

Ha látok egy madarat, amely úgy jár, mint egy kacsa, úszik, mint egy kacsa, és hápog, mint egy kacsa, azt a madarat kacsának nevezem.

Tehát ezt feltételezhetjük

Ha egy objektum prociként működik (a call metódusra reagálva), akkor folytassa és kezelje procikként.

Nyugodtan tekintse meg a Miért ösztönzi a Ruby közösség a kacsagépelést cikket, ha nem ismeri ezt a tervezési elvet.

Funkció Kompozíció a színfalak mögött

A Proc#>> és Proc#<< metódusok a Proc új példányát adják vissza, amely végrehajtja az első proc-t, és ennek a proc hívásnak a visszatérési értékét adja át a második proc< argumentumként. /em> végrehajtásához. Ez a mechanizmus akkor lép működésbe, amikor meghívja a call-t ezen a frissen létrehozott procin

a = proc { 42 }
b = proc { |x| x * 2 }
composed_proc = (a >> b) # => #<Proc:0x00007f95820bb908>
composed_proc.call       # => 84

Itt láthatjuk, hogy a a >> b egy új proc-ot ad vissza, és amikor meghívjuk a composed_proc.call-t, akkor a b.call(a.call) végrehajtásra kerül.

Most pedig nézzük meg, mi történik, ha kompozíciót használunk lambdákkal és procokkal

a = lambda { |x| x * 2 }
b = proc   { 42 }
composed_proc = (a << b) # => #<Proc:0x00007f99fd054a80(lambda)>
composed_proc.call       # => 84

Itt láthatjuk, hogy a composed_proc egy lambda. Valójában a bal oldali proc típusa határozza meg a composed_proc típusát. Tehát, mivel a a egy lambda, úgy a composed_proc is egy lambda. Most fordítsuk meg a a és b összetételt

a = lambda { |x| x * 2 }
b = proc   { 42 }
composed_proc = (b << a) # => #<Proc:0x00007f99fd054a80>
composed_proc.call

termel

ArgumentError (wrong number of arguments (given 0, expected 1))

Itt a b egy proc. A Socomposed_proc szintén egy proc. A ArgumentError azért jelenik meg, mert a composed_proc.call először a a.call-ot hívja meg, és mivel a a egy lambda, szigorúan argumentumra számít.

Ez egyben a bal oldali proc, amely meghatározza az összeállított proc típusát a Proc#>> metódus használatakor. Valójában, mivel a bal oldali proc a Proc#>> és Proc#<< üzenet receiver-je, automatikusan meghatározza a visszaadott proc típusát a self típusának megfelelően — az a példány, amely ezt a metódushívást fogadja.

Ezt a viselkedést a Ruby 2.7.1-ig vállalhatja. Úgy tűnik, hogy a Ruby következő verziója kissé módosítja ezt a viselkedést azáltal, hogy a jobb oldali proci típusát használja a Proc#<< metódushoz.

Voálá!