表題のとおりです。
おっさんなのにわりと知らないことも多くて、不勉強を実感しました。
HaskellのGHC.Genericsはあまりよくわかってないので、ちゃんとやるべきですね…
なお、当日のツイートは #ジェネリクス勉強会 since:2017-06-24 until:2017-06-25
で検索すれば見つかると思います。
で、 @kmizu さんがした、抽象型でジェネリクスの主要な概念を実装するという 発表内容 が面白く、高階型もエミュレートできるとのことでしたので、家に帰ってから発表されてた内容を思い出しつつ自分でも書いてみました。
題材としては Functor
(二番煎じ) + Maybe Functor(実装が簡単)を用いています。
まず関数を定義します。
trait Fun1 { type I type O def apply(i: I): O }
次に高階型を定義します。(発表中に気づいて、「なるほど」と思った)
trait Hk1 { type E }
次に Functor
を定義します
trait Functor {self => type F <: Hk1 type A type B //noinspection ApparentRefinementOfResultType def map(o: F {type E = self.A}, f: Fun1 {type I = self.A; type O = self.B}): F { type E = self.B } def map(f: Fun1 {type I = self.A; type O = self.B}): Fun1 { type I = F { type E = self.A } type O = F { type E = self.B } } = new Fun1 { override type I = F { type E = self.A } override type O = F { type E = self.B } override def apply(input: F { type E = self.A }): F { type E = self.B } = self.map(input, f) } }
ちなみに僕は初めて trait Foo {self =>
と書いたときの self
が何を指しているのかがわかりました
@kmizuさんの発表では最初の map
関数だけ定義されていましたが、Haskellで fmap f
を新たに関数として用いているパターンを見かけるので、 map(f: A => B): F[A] => F[B]
となるような関数も定義してみました(完全に自己満足です)。
で、 Maybe
型を定義します。
trait Maybe extends Hk1 { def getValue: E }
とりあえず、具体的に Int
の Maybe
を定義します
object NothingInt extends Maybe { override type E = Int override def getValue: Int = throw new NoSuchElementException("this is Nothing") } case class JustInt(value: Int) extends Maybe { override type E = Int override def getValue: Int = value }
同様に String
の Maybe
も定義します
object NothingString extends Maybe { override type E = String override def getValue: String = throw new NoSuchElementException("this is Nothing") } case class JustString(value: String) extends Maybe { override type E = String override def getValue: String = value }
ジェネリクスを使えば Just[A]
と Nothing
を定義すれば済みますが、型1つずつに型をつくらないといけない辺りが実用性が…と感じられますが、コンピューターが自動でこのようなボイラープレートなコードを生成するのであれば気にならないと思います(コンパイル時に型を決定してこれと同様なバイナリを生成するなどの作戦はあるかと思う)。
そして Functor
のインスタンスを作ります
object MaybeIntToStringFunctor extends Functor { override type F = Maybe override type A = Int override type B = String override def map(o: Maybe { type E = Int }, f: Fun1 { type I = Int ;type O = String }): Maybe { type E = String } = o match { case NothingInt => NothingString case JustInt(v) => JustString(f(v)) } }
では、実際にこれを使ってみたいと思います。
// Fun1 を作るのが面倒なので、通常の関数から Fun1 を作る関数を作る def fun[A,B](f: A => B): Fun1 { type I = A; type O = B } = new Fun1 { type I = A; type O = B; override def apply(i: I): O = f(i) } // とりあえず、 Functor のインスタンスを implicit な変数にあれする implicit val maybeIntToStringFunctor = MaybeIntToStringFunctor // map という関数を作って Functor の map 関数を呼べるようにする def map(f: Fun1{ type I = Int; type O = String})(implicit tci: Functor { type A = Int; type B = String }): Fun1{ type I = tci.F { type E = Int }; type O = tci.F { type E = String } } = new Fun1 { type I = tci.F { type E = Int } type O = tci.F { type E = String } override def apply(i: I): O = tci.map(i,f) } // map関数にFun1を適用して Maybe をmappingする関数を作る val intToStringMapper = map(fun(i => "the value is " + i)) // ↑の関数に JustInt(10) を適用する intToStringMapper(JustInt(10)) // -> JustString("the value is 10") // ↑の関数にNothingInt を適用する intToStringMapper(NothingInt) // -> NothingString
という感じでScalaよくわかってませんが作れました
この Functor
ですが、 String
から Int
にマップする場合にはまた別の Functor
のインスタンスを作る必要があり、この形で人間がコードを書くのは若干辛いものがありました…(本当はモッを作ろうとしたけど、やめた)