Scala - typově bezpečné eventy
18. 2. 2011 (před 12 lety) — k47 (CC by-nc-sa)

Scalovská obdoba eventů z C#.
Eventy mají správnou varianci (kontravariantní v argumentech, návratový typ v tomhle případě není důležitý), která byla „doladěna“ anotací @uncheckedVariance
, která říká kompilátoru, ať si nestěžuje, že se v definici metod +=
a -=
nemůže vyskytovat daný typ s danou variancí. Ale přesto přezevšechno je zajištěno, že argumenty volání metody apply
jdou kontravariantní.
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 Programming in Scala.
Pokud o tenhle článek zakopne nějaký odborník typové teorie: jsou moje úvahy skutečně korektní nebo je někde skrytá díra?