k47.cz  — každý den dokud se vám to nezačne líbit
foto Praha výběr povídky kultura
twitter FB RSS

Scala - typově bezpečné eventy

18. 2. 2011 — k47 (CC by-nc-sa) (♪)

Sca­lov­ská obdoba eventů z C#.


Eventy mají správ­nou va­ri­anci (kon­tra­va­ri­antní v ar­gu­men­tech, ná­vra­tový typ v tomhle pří­padě není dů­le­žitý), která byla „do­la­děna“ ano­tací @uncheckedVariance, která říká kom­pi­lá­toru, ať si ne­stě­žuje, že se v de­fi­nici metod +=-= nemůže vy­sky­to­vat daný typ s danou va­ri­ancí. Ale přesto pře­ze­všechno je za­jiš­těno, že ar­gu­menty volání metody apply jdou kon­tra­va­ri­antní.

object Event {
  import scala.annotation.unchecked.{ uncheckedVariance => uv }
  import scala.collection.mutable.ListBuffer

  trait F[T] {
    protected[this] val fs = ListBuffer[T]
    def +=(f: T) { fs += f }
    def -=(f: T) { fs -= f }
  }

  class F0 extends Function0[Any] with F[Function0[Any]] {
    def apply() = fs.foreach(_())
  }

  class F1[-A] extends Function1[A, Any] with F[Function1[A @uv, Any]] {
    def apply(a: A) = fs.foreach(_(a))
  }

  class F2[-A, -B] extends Function2[A, B, Any] with F[Function2[A @uv, B @uv, Any]] {
    def apply(a: A, b: B) = fs.foreach(_(a, b))
  }

  class F3[-A, -B, -C] extends Function3[A, B, C, Any] with F[Function3[A @uv, B @uv, C @uv, Any]] {
    def apply(a: A, b: B, c: C) = fs.foreach(_(a, b, c))
  }

  class F4[-A, -B, -C, -D] extends Function4[A, B, C, D, Any] with F[Function4[A @uv, B @uv, C @uv, D @uv, Any]] {
    def apply(a: A, b: B, c: C, d: D) = fs.foreach(_(a, b, c, d))
  }

  class F5[-A, -B, -C, -D, -E] extends Function5[A, B, C, D, E, Any] with F[Function5[A @uv, B @uv, C @uv, D @uv, E @uv, Any]] {
    def apply(a: A, b: B, c: C, d: D, e: E) = fs.foreach(_(a, b, c, d, e))
  }

}
// použití
import Event._

val f = new F1[Int]
f += println
f += { (a: Int) => println(a * a) }
f(10) // vytiskne 10 a 100

val callback = println(_: Int)
f += callback
f(10) // vytiskne 10, 100 a 10

f -= callback
f(10) // vytiskne 10 a 100


// Aby bylo možné odebrat callback přes `-=`, musíme si přidanou funkci někam uložit,
// protože identita funkcí se dá porovnat jenom shodou referencí na funkční objekt.

f += println(_: Int)
f -= println(_: Int) // neudělá nic, odebírá se jiný objekt, než se předtím přidal


class A
class B extends A
class C extends B

val f = new F1[B]
f += { a: A => println("typ A") } // ok
f += { b: B => println("typ B") } // ok
f += { c: C => println("typ C") } // chyba během kompilace!
                                  // porušení kontravariance argumentu
                                  // přidávaná funkce počítá s typem C, ale f může zaručit jenom B

f(new C)
f(new B)
f(new A) // chyba během kompilace! f vyžaduje aspoň B

Help: sekce 19 knihy Pro­gra­m­ming in Scala.


Pokud o tenhle článek za­kopne nějaký od­bor­ník typové teorie: jsou moje úvahy sku­tečně ko­rektní nebo je někde skrytá díra?

píše k47 & hosté, ascii@k47.cz