[Scala] OOP Polimorfizmus vs FP Monomorfizmus

Hozzászólások

Szép, viszont az FP megvalósításban itt nem tetszik a Draw függvény rugalmatlansága: minden "származtatott" osztályt bele kell tenni az implementációba.

Szerintem sokkal rugalmasabb a "draw" függvény az FP-ben, akár hány "draw" függvényt készíthetsz, amikbe szépen össze tudod válogatni, hogy melyik mit csinál. OOP-ben a "draw" az az FP-beli "export"-nak felel meg, de FP-ben van "draw", "print", ... akármennyi kombinációd az egyes függvényekből.

Én ilyet csináltam múltkor: OOP, FP

Gyakorlatilag a függvény típus az interface, azt úgy valósítod meg ahogy akarod. Szerintem szebb, mint az OOP megoldás.

Szerintem is. :)

Gyakorlásként megcsináltam a set függvényt, amivel változónak adhatsz értéket.


  val set = (variable: Variable, value: Float, expression: Expression) =>
    (context: Context) =>
  {
    val newContext: Context = {
      case `variable` => value
      case v => context(v)
    }
    expression(newContext)
  }

 ...

  val expression = set(d, 4f, plus(minus(multiply(divide(value(a), value(b)), value(c)), value(d)), sqrt(constant(25f))))

  val context = Map(a -> 1f, b -> 2f, c -> 3f)
  println(expression(context))

A pattern match az FP specifikus?

Egyebkent gratulalok, lenyegeben feltalaltad az Erlang fuggvenyhivasi modszeret (csak a Scala syntax zajosabb es bobeszedubb - talan a megfelelo megoldas egy makro lenne, es akkor nem kellene a PartialFunction-nal sem szorakozni a kodban)! ;)

"A pattern match az FP specifikus?"

Igen, jellemzően FP nyelvekben van, bár egyre több nyelv fogad be FP specifikus dolgokat.

"Egyebkent gratulalok, lenyegeben feltalaltad az Erlang fuggvenyhivasi modszeret"

Nem én találtam fel, hanem Martin Odersky.

"nem kellene a PartialFunction-nal sem szorakozni a kodban"

A PartialFunction-nek megvannak az előnyei:
- lekérdezhető, hogy adott inputot kezel-e,
- tetszőleges inputra képes Option választ adni,
- könnyű más PartialFunction-ökkel bővíteni a tudását,
- mivel rendes függvény, ezért az FP jóságai erre is vonatkoznak (higher order, compose, ...)

Erdekes, en most az arckaparos fazisban vagyok: egyre gyakrabban anyazok a monitorra, hogy miert kell nekem hatracsavart kezzel fejen ugralni csak azert, hogy a fordito megertse, mit akarok.

Ehhez kepest konstans flow barmilyen fejlettebb dinamikus nyelvben kodolni: Erlang, Clojure, sot (nem hiszem el, de le fogom irni) Javascript!

Remelem, majd elmulik, mert jovo heten lesz egy scalas pair programmingos interjum, nem mutatna tul jo kepet, ha menet kozben a falhoz vagnam a billentyuzetet :)

A történethez hozzátartozik, hogy az FP részét egyelőre kevéssé használom ki. Pattern matching, higher order functions, ezek vannak, de azért dominámsan imperatív vagyok.
Viszont a feladatot (kutatási téma, ha lesz eredmény, beszámolok) nagyon jól lehet actorokkal modellezni.

Szoval megegyszer, mitol FP-specifikus a pattern match? Nem kotozkodom, komolyan erdekel, peldaul pont a Scalaban egy objektum metodusa (az unapply) teszi lehetove, hogy tetszoleges tipuson matcholj, szoval epphogy semmi koze a FP-hoz ezen a szinten. De valoban leginkabb FP nyelvekben jellemzo, viszont nem latom, miert ne lehetne akar a Java forditot kiegesziteni egy unapply-ertelmezovel. Jo kerdes persze, hogy hol venned hasznat ezutan :)

Ha a Scala Erlang lenne, akkor peldaul ez:


type ExportShape = PartialFunction[Shape, String]

val drawCircle: ExportShape = {
case Circle(point, radius) =>
s"Drawing circle at (${point.x}, ${point.y}) Radius: $radius"
}

valahogy igy nezne ki:


def drawCircle(Circle(point, radius)) =>
s"Drawing circle at (${point.x}, ${point.y}) Radius: $radius"

meg tovabb egyszerusitve:


def draw(Circle(point, radius)) =>
s"Drawing circle at (${point.x}, ${point.y}) Radius: $radius"

def draw(Rectangle(point, width, height)) =
s"Drawing rectangle at (${point.x}, ${point.y}) Width $width, Height $height"

vagyis


def draw(Circle(Point(x, y), radius)) =>
s"Drawing circle at (${x}, ${y}) Radius: $radius"

def draw(Rectangle(Point(x, y), width, height)) =
s"Drawing rectangle at (${x}, ${y}) Width $width, Height $height"

stb.

Erre batorkodtam csak celozni. Sajnos Martin megallt valahol feluton...

Nem igazán értem mire akarsz kilyukadni. Annyira FP specifikus, mint a függvény, az immutabilitás, a higher order függvények, a lambda függvények, ... Ezek mind lehetnek OOP vagy imperatív nyelvekben is. Az FP nyelvekben jellemzően benne van a pattern matching, míg az OOP nyelvekben általában (még) nincs.

Az Erlang-os példa meg azért nem igazán illik ide, mert az Erlang egy dinamikusan típusos nyelv, míg a Scala statikusan. Nyilván sok mindent egyszerűbb (rövidebb) megírni dinamikus nyelven, mint statikuson. A statikusnak meg más előnye van. (alma-körte esete)

"Sajnos Martin megallt valahol feluton..."

Az biztos, hogy lehetne rajta javítani, de nem biztos, hogy ezt egyszerű megtenni. A PartialFunction típusát nem kell megadni, ha anomymus függvényként, paraméterként adod meg, de nevesítve már meg kell. Ez utóbbinak egyszerűsítése meg is jelent kérésként a Scala fejlesztői felé.

Ha egy alapvetoen FP(-nek gondolt) nyelvbol elveszed a pattern matchinget, attol meg FP marad? Ha egy alapvetoen OOP(-nek gondolt) nyelvhez hozzaadod a pattern matchinget, attol FP-jellegu lesz? Tegyuk fel ugyanezt a ket kerdest mondjuk a higher order fuggvennyel is. Egyebkent nem akarok kilyukadni semmi konkretumra, csupan hangosan gondolkodtam, elnezest :)

A masik temara pedig: mi tortenne, ha irna valaki egy makrot, ami a fenti Erlang-szeru szintakszisbol automatikusan legyartana a PartialFunction tipust, atalakitana a zajmentes parameter-matchelest a fuggveny={case => } formatumra, majd a vegen compose-olna a fuggvenyeket.

Emelt szintu feladatkent pedig az egymasba agyazott pattern matchet is kibontana megfeleloen (tehat a fenti Circle(Point(x,y)) mukodhetne). Vagy ez mar tul durva?

Persze siman lehet, hogy nyitott kapukat dongetek, es valamelyik furmanyos library ezt mar reg tudja...

"A masik temara pedig: mi tortenne, ha irna valaki egy makrot, ami a fenti Erlang-szeru szintakszisbol automatikusan legyartana a PartialFunction tipust, atalakitana a zajmentes parameter-matchelest a fuggveny={case => } formatumra, majd a vegen compose-olna a fuggvenyeket."

Én örülnék neki, ha lenne valami hasonló! :)

"Emelt szintu feladatkent pedig az egymasba agyazott pattern matchet is kibontana megfeleloen (tehat a fenti Circle(Point(x,y)) mukodhetne)"

Ez most is működik, mármint az egymásba ágyazott case class-ok.
Pl.:


val shape = Circle(Point(10, 15), 27)
shape match {
  case Circle(Point(x, y), radius) => s"Match! $x, $y"
}                                                                                               

res0: String = Match! 10, 15                                                                          

A másik részét meg valószínűleg nem is lehet megcsinálni, mert nem lehet kitalálni az inputok típusát.
Circle input esetén ugyanúgy lehet Circle, Shape vagy Any is.

Egyébként az Any típus használatával majdnem a dinamikusság szabadságát kapod, viszont elvesztve a statikusságot, a fordítás idejű ellenőrzést.
Pl.


type PF = PartialFunction[Any, Any]

esetén, akármilyen input és outputtal használhatod, pl.:


val calc: PF = { case x: Int => x * 2 }
val drawCircle: PF = { case (Display(), Circle(Point(x, y), radius)) => s"Draw ..." }

Használata:


calc(6)  // eredmény: 12
drawCircle(Display(), Circle(Point(10, 3), 24)) => s"Draw ..." }  // eredmény: Draw ...

// akár ezt is lehet:
val calcOrDraw = calc orElse drawCircle

calcOrDraw(7)  // eredmény: 14
calcOrDraw(Display(), Circle(Point(10, 3), 24))  // eredmény: Draw ...

Bámulatos, mennyire túl lehet bonyolítani, ha nagyon akarja az ember :)