Scala implicit konverzió helyettesítése

Az implicit konverziónak két fontos előnye van:
1. Látszólag kiegészíthetjük egy osztályunkat új metódusokkal
2. Szép DSL-ek készíthetők vele

A mostani megvalósításra szép leírást találunk itt: Add Your Own Methods to the String Class

Nekem ez az implicit konverzió nem igazán tetszik. Feleslegesnek érzem, hogy egy plusz osztályt származtassunk a meglevőből. Illetve csak az OO részt lehet DSL-ként használni, a "funkcionális" részt nem. A DSL kinézetet akkor kapjuk, ha egy osztály egy metódusát egy paraméterrel (vagy paraméter nélkül) hívjuk, akkor elhagyható a pont és zárójel, tehát az OSZTÁLY.metódus(PARAMÉTER) használható így is: OSZTÁLY metódus PARAMÉTER.

A funkcionális irányból megközelítve a dolgot, egy osztályt úgy tudunk kiegészíteni, hogy plusz függvényeket készítünk hozzá.

Ha a link-nél is szereplő String kiegészítéseket akarnánk megcsinálni, akkor azt így tehetnénk meg funkcionálisan:


object StringImprovements {
  def increment(s: String) = s.map(c => (c + 1).toChar)
  def decrement(s: String) = s.map(c => (c − 1).toChar)
  def hideAll(s: String) = s.replaceAll(".", "*")
}

Így tudnánk használni, pl.


StringImprovements.increment("HAL")

Ha beimportáljuk a StringImprovements-et,


import StringImprovements._

akkor így:


increment("HAL")

Kiegészítésnek még majdnem elmenne, de szép DSL-re nem tudnánk használni.
Itt jön az ötlet! Mi lenne, ha az object-ek (singleton) metódusait metódus(PARAM1, PARAM2, ...) helyett ilyen alakokban is meg lehetne hívni: PARAM1.metódus(PARAM2, ...), tehát mintha az első paraméternek lenne a metódusa, illetve, ha maximum két paramétere van, akkor a fentiek alapján így is: PARAM1 metódus PARAM2. Ezzel elhagyható lenne az implicit konverzió és a függvényeket is használhatnánk DSL-ként.

Az első példát ilyen módokon is hívhatnánk:


"HAL".increment
"HAL" increment

, tehát pontosan úgy, mint implicit konverzió esetén.

Ha pl. akarunk egy olyan függvényt készíteni, ami egy egész listát leszűr egy adott egész számnál nagyobbakra.


def greaterThan(numbers: List[Int], number: Int) = numbers.filter(_ > number)

, akkor azt így is meg tudnánk hívni:


val numbers = List(1, 9, 3, 6, 4, 2, 5)
val numbersGreaterThan4 = numbers greaterThan 4

Két további előnye is van:

  1. A fordítónak egyszerűbb dolga van, mint az implicit konverziónál. Implicit konverziónál, ha a.f(b) formula nem fordul, akkor körbe kell néznie az összes implicit konverziós osztályban, hogy van-e valamelyikben f metódus. Ez elég lassú és kérik is, hogy minél kevesebb implicit konverziós osztályt használjunk éppen ezért. A mi esetünkben viszont a fordítónak elég megnéznie, hogy az f(a, b) függvény létezik-e, ami nagyon gyors.
  2. Az implicit konverziós osztálynál előbb létre kell hozni az adott osztályt, majd meghívni egy metódusát. A mi esetünkben kimarad az osztály létrehozás, elég csak a metódust futtatni. Így a futás is valamelyest gyorsabb, mint implicit konverziónál.

Örömmel veszem a véleményeket!

Hozzászólások

Kezdő vagyok a témában, úgyhogy ez inkább kérdés mint válasz.

A "PARAM1" is csak egy függvény, mint minden más, ahogy az első könyvemből kivettem. Azt a típust kellene kiegészítened a kívánt fügvényekkel és program indításnál felülírni a ClassLoader-ben az eredetit a tiéddel.

"HAL".increment esetén plusz függvényt írsz, "HAL" increment meg csak egy operator overload.

Szóval szerintem erre nincs nyelvi megoldás.

Azt hiszem szokás szerint nem voltam teljesen világos.

Egy új Scala fordítóprogrambeli feature-t szerettem volna felvetni, tehát, hogy milyen jó volna, ha a Scala fordító tudná ezt is, így az implicit konverziót el lehetne felejteni, valamint a függvényeket is be lehetne fogni a DSL definiálásába.

Ez inkább haladó Scala-soknak szól.

edit:
ok, hülye módon nem olvastam el a linkelt cikket. A lenti példa van ott is.

Egy objektum metódusa egy függvény, aminek az első paramétere implicit, neve `this`.

Implicit paraméterek mellett implicit osztályokat is lehet használni DSL-hez:


object StringImprovements {
implicit class StringEx(s: String) {
def increment = s.map(c => (c + 1).toChar)
def decrement = s.map(c => (c - 1).toChar)
def hideAll = s.replaceAll(".", "*")
}
}
object StringTest extends App {
import StringImprovements._
println("hello".increment)
println("hello".decrement)
println("hello".hideAll)
}