k47.cz
mastodon twitter RSS
bandcamp explorer

Scala - typově bezpečné eventy

— 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 +=-= 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?

píše k47, ascii@k47.cz