F#に型クラスを入れる実装の話
この記事はF# Advent Calendar 2016の11日目のものです。ちょっと遅れてしまいました。。。
ICFP 2016(と併催されたML workshop?)で気になる内容があったので、ちょっとまとめてみました。
Classes for the Masses - ICFP 2016
ざっくり、F#に型クラスを導入してみたぜ、って内容です。
型クラスとは
JavaやC#での interface
みたいなものですが、interface
は侵入的なのに対して、型クラスは非侵入的という違いがあります。
侵入的というのは、型の定義にその interface
を実装しますよ、ということを書く必要があることを意味します。
// C# interface Eq<A> { bool Equal(A a, A b); } // intefaceは型に侵入する class SomeClass : Eq<SomeClass> { public bool Equal(A a, A b) { ... } }
それに対して型クラスは非侵入的であり、型の定義にその型クラスを実装することは書きません。
-- haskell class Eq a where (==) :: a -> a -> Bool -- 型クラスは型の定義に書かなくていい data SomeType = ... -- SomeTypeをEq型クラスのインスタンスにする(型定義と分かれている) instance Eq SomeType where x == y = ...
これの何が嬉しいかというと、ひとつは、型の定義をその型に対して可能な操作と分離できることです*1。
これによって、例えば標準ライブラリの型に対しても、後付けで型クラスのインスタンスにできるようになります。 抽象を後付けできるとでも表現すればいいでしょうか。
この型クラスをF#に導入してみた、というのが今回紹介する内容です。
F#への型クラスの実装方法
実際に動作するコードは下記のリポジトリで公開されています。
先に示した Eq
型クラスはこの実装を使うと、
// Eq型クラスの実装(interfaceとしてコンパイルされる) [<Trait>] type Eq<'a> = abstract equal: 'a -> 'a -> bool // SomeTypeをEq型クラスのインスタンスにする(structとしてコンパイルされる) // Haskellと違い、インスタンスの定義に名前(ここではEqSomeType)が必要 [<Witness>] type EqSomeType = interface Eq<SomeType> with member equal x y = ...
と書きます。
この Eq
型クラスを使うには、
let (==) a b = Eq.equal a b
のように、型クラス名.メンバー 引数リスト ...
のように書くようです。
型クラスは struct
にコンパイルされるため、デフォルト値を介して型クラスのメンバーにアクセスできます。
この関数は、下記のようにコンパイルされます。
// C#モドキ public static bool operator ==<A, EqA>(A a, A b) where EqA : struct, Main.Eq<A> => default(EqA).equal(a, b);
struct
を使うことで、追加の引数を不要にしています。
また、Eq
型クラスを要素に持つリストを Eq
型クラスのインスタンスにする(それ以外のリストは Eq
型クラスにしない)こともできます。
[<Witness>] type EqList<'a, 'EqA when 'EqA :> Eq<'a>> = interface Eq<'a list> with member equal a b = match a, b with | x::xs, y::ys -> Eq.equal a b && Eq.equal xs ys | [], [] -> true | _, _ -> false
互換性及び他の.NET言語との連携
ここまででみたように、この実装ではあくまで.NETの型でそのまま表現できる形になっています。 ランタイムに手を加える必要がないため、互換性を崩すことなく採用できるように実装されている、ということです。
また型クラスを使った関数は、型クラスに対応しない既存の.NET言語からは型パラメータを明示的に渡せば使えます(使いやすいとは言っていない)。
この方法の問題点
この方法はランタイムに手を加えないため、(例えば Monad
のような)高階型クラスがサポートできません。
ううむ、残念・・・
あ、それと、このリポジトリをcloneしてbuild.cmdを管理者権限で実行するとF#の環境がぶっ壊れた(VSでビルドできなくなった)ので、やるなら仮想環境で試してみることをお勧めします。