Scala - jópofa dolgok a trait-ek

Előjött egy olyan feladat, hogy egy "kibővíthető" metódusra van szükségem egy osztályon belül.
Van egy alapfunkcionalitás: nem csinál semmit. Ezt szeretném kibővíteni különböző dolgokkal: loggoljon, üzenetet küldjön valaki másnak, ábrázoljon grafikonon, whatever. Lényeg: ezek tetszőleges kombinációját kellene tudnia csinálni. És erre gyönyörűen működik a következő kód:


trait Base { 
  private def _name() = "Base"
  def func = _name
} 
trait A extends Base { 
  private def _name() = "A"
  override def func = _name + " extends " + super.func
}
trait B extends Base { 
  private def _name() = "B"
  override def func = _name + " extends " + super.func
}
val x = new Base with A with B
val y = new Base with B with A

println(x.func)
println(y.func)

Hogy mi készül éppen? Demonstrációs célokból szükségem van egy egyszerű Network-on-a-Chip szimulátorra, és úgy gondolom, az Actor model ennek leírására kiválóan használható (esetünkben Akka).

Scala rulez, teljesen rajongója lettem.

Hozzászólások

Örülök, hogy neked is tetszik!

Annyi kiegészítést tennék a kódhoz, hogy nem szokás aláhúzással kezdeni az azonosítókat, illetve ami nem függvény, hanem konstans, azt lehet val-lal definiálni akkor is, ha a szülőben def-volt, mint itt a name, illetve nem szükséges a végére a zárójel, ahogy a func végére se tettél. (A func végére inkább lehetne rakni, mert az tényleg függvény, szemben a name-mel).

Én inkább ilyenre írnám:


trait Base { 
  private def name = "Base"
  def func = name
} 
trait A extends Base { 
  private val name = "A"
  override def func = name + " extends " + super.func
}
trait B extends Base { 
  private val name = "B"
  override def func = name + " extends " + super.func
}
val x = new Base with A with B
val y = new Base with B with A

println(x.func)
println(y.func)

Egyébként ezekben a témakörökben (Scala, Akka, FP) mindig szívesen segítek, ha bármi kérdésed lenne!

Így már értem, hogy pontosan mire gondoltál. Azt hittem az extrémet szélsőséges értelemben érted, vagyis, hogy meg lehet csinálni úgy is, de nem igazán jó megoldás. Holott a példádból is látszik, hogy egyszerűbb és sokkal általánosabban, rugalmasabban használható az FP megoldás, mint az OO.

Amire folyamatosan rádöbbenek: eszméletlenül kifejező nyelv, nagyon jóleg tudom vele fogalmazni a dolgokat.

Egy valami hiányzik most: generikus osztályban az adott típusból példány létrehozása. No jó, meg a parametrizálható trait-ek, de azt tagváltozókkal meg lehet oldani.

"generikus osztályban az adott típusból példány létrehozása."

Ez alatt mit értesz? Esetleg egy példa is jó volna, hogy mire használnád!

Szerk.: Közben rájöttem, hogy valószínűleg a generikus típusból szeretnél létrehozni egy példányt az osztályon belül. Erre az a megoldás, hogy megadsz az osztályodnak egy factory függvényt, ami létrehozza, lásd itt.

A fordítási időben még ismert típushoz tartozó Manifest-et a fordító képes fordítási időben implicit elérhetővé tenni, így futás időben olyan információt ad a típusról, ami egyébként a type erasure miatt elveszne. Ennek csak egy aspektusa az, hogy elérhetővé válik a típushoz tartozó Class objektum.

Ahogy írtam, a Manifest-et implicit adja oda a fordító, a lenti két metódus (majdnem) ekvivalens:


def foo[T : Manifest] = manifest[T].toString

def bar[T](implicit mf: Manifest[T]) = mf.toString

A fenti kódban a manifest[T] visszaadja az implicit elérhető Manifest[T]-t, így lehetne implementálni (ez egy gyakori trükk Scalaban implicitek környékén, pl. az Ordering is tud ilyesmit):


def manifest[T](implicit mf: Manifest[T]) = mf

A Manifestben az a jó, hogy a fordító mindjárt tudja is használni, pl: http://ideone.com/VUcCfB

ez menő. viszonylag sokat gondolkoztam mostanság azon, miért nem csináltak valami hasonlót Java-ban - ha már a generikusok csak így kókányolva kerültek be a nyelvbe. s hogy van-e, lenne-e bármilyen akadálya, akár nyelvi szinten, akár JVM szinten. De ezek szerint JVM szinten nincs.

az ideone-os példában azt kéne látnom, hogy a fordító a generikus típus alapján tudja, hogy a két függvényhívás közül (

safePrint2[Int](iToS) orElse safePrint2[Boolean](bToS)

) melyiket is kell majd meghívnia?

Ez bájtkódban hogy jelenik meg? A safePrint2 függvényből kettő lesz bátjkód szinten?

Mindenesetre ez bookmark, köszi szépen - ha lesz egy kis időm, a vizsgaidőszak után, alaposabban belemélyedek ebbe, elég érdekesnek tűnik.
--
blogom

"a fordító a generikus típus alapján tudja, hogy a két függvényhívás közül (

safePrint2[Int](iToS) orElse safePrint2[Boolean](bToS)

) melyiket is kell majd meghívnia?

Ez bájtkódban hogy jelenik meg? A safePrint2 függvényből kettő lesz bátjkód szinten?"

A safePrint2 egy PartialFunctiont (továbbiakban PF) ad vissza, ami egy olyan függvény, ami csak a paraméterei értelmezési tartományának egy részén értelmezett, ezt írja le a case utáni kifejezés. Jelen esetben azt mondtam meg, hogy az x típusa legyen T, miközben maga a PF Any-t (vagyis java Objectet) vár. Az implicit Manifest[T]-ből JVM szinten egy explicit paraméter lesz, ami majd a hívás helyén kerül behelyettesítésre a fordító által. Ezt a Manifestet használja a PF implementációja belül, így tudja megkülönböztetni a paramétereket típus alapján futásidőben. Bytecodeban most nem tudod megmutatni, de decompileolva így néz ki (az evidence$1 használata az érdekes):


public <T> PartialFunction<Object, BoxedUnit> safePrint2(Function1<T, String> toString, final Manifest<T> evidence$1) {
  new AbstractPartialFunction(toString, evidence$1) {
    public static final long serialVersionUID = 0L;
    
    public final boolean isDefinedAt(Object x2) {
      Object localObject = x2;
      Option localOption = this.evidence$1$1.unapply(localObject);
      boolean bool;
      if ((!localOption.isEmpty()) && ((localOption.get() instanceof Object))) {
        bool = true;
      } else {
        bool = false;
      }
      return bool;
    }
    
    public final <A1, B1> B1 applyOrElse(A1 x2, Function1<A1, B1> default) {
      final Object localObject1 = x2;
      Option localOption = this.evidence$1$1.unapply(localObject1);
      Object localObject2;
      if ((!localOption.isEmpty()) && ((localOption.get() instanceof Object))) {
        Predef..MODULE$.println(Try..MODULE$.apply(new AbstractFunction0() {
          public static final long serialVersionUID = 0L;
          
          public final String apply() {
            return (String)X..anonfun.safePrint2.1.this.toString$2.apply(localObject1);
          }
        }));
        localObject2 = BoxedUnit.UNIT;
      }
      else {
        localObject2 = default.apply(x2);
      }
      return (B1)localObject2;
    }
  };
}

Így kerül meghívásra:


safePrint2(new AbstractFunction1() {
  public static final long serialVersionUID = 0L;
  
  public final String apply(int x) {
    return X..this.iToS(x);
  }
}, ManifestFactory..MODULE$.Int())

Az orElse egy sima metódus a PF-ön, ami létrehoz egy új kompozit PF-t a this-ből és a paraméterből. Ebben nincs semmi compiler magic, egyszerűen megpróbálja végrehajtani az első PF-t, ha az nem sikerül, fallbackel a másodikra.