Freer Effectsが、だいたいわかった: 11-8 FlexibleContexts拡張
はじめにの前に
FlexibleContexts拡張について記事を書くのは、むずかしい。FlexibleContexts単体では、あまり意味のある例題が作れない。MultiParamTypeClasses拡張などによって定義されたクラスを使って、型クラス制約を組み立てるときなどに、Haskell標準での「型クラス制約に許されたかたち」では力不足になる。そこで、FlexibleContexts拡張が必要になる。なので、FlexibleContextsに特化した魅力的な例題というのは作りにくい。
そこで、FlexibleContexts拡張に特化した例題ではなく、いままで紹介してきた言語拡張やプラグマもあわせて、おもしろそうな話題を考えてみた。ここで使う言語拡張やプラグマは、以下のとおり。
- MultiParamTypeClasses拡張
- FlexibleInstances拡張
- FlexibleContexts拡張
- INCOHERENTプラグマ
目次
(0). 導入
-
Freeモナドの概要
- Freeモナドとは
- FreeモナドでReaderモナド、Writerモナドを構成する
- 存在型(ExistentialQuantification拡張)の解説
- 型シノニム族(TypeFamilies拡張)の解説
- データ族(TypeFamilies拡張)の解説
- 一般化代数データ型(GADTs拡張)の解説
- ランクN多相(RankNTypes拡張)の解説
-
FreeモナドとCoyoneda
- Coyonedaを使ってみる
- FreeモナドとCoyonedaを組み合わせる
- いろいろなモナドを構成する
-
Freerモナド(Operationalモナド)でいろいろなモナドを構成する
- FreeモナドとCoyonedaをまとめて、Freerモナドとする
- Readerモナド
- Writerモナド
- 状態モナド
- エラーモナド
-
モナドを混ぜ合わせる(閉じた型で)
- Freerモナドで、状態モナドとエラーモナドを混ぜ合わせる
- 両方のモナドを一度に処理する
- それぞれのモナドを、それぞれに処理する
- Freerモナドで、状態モナドとエラーモナドを混ぜ合わせる
- 存在型による拡張可能なデータ構造(Open Union)
- 追加の言語拡張
- Open Unionを型によって安全にする
- モナドを混ぜ合わせる(開いた型で)
- FreeモナドとOpen Unionを組み合わせる
- 状態モナドにエラーモナドを追加する
- Freer Effectsで、IOモナドなどの、既存のモナドを使用する
- 関数を保管しておくデータ構造による効率化
- いろいろなEffect
- 関数handleRelayなどを作成する
- NonDetについてなど
はじめに
型変数のとりうる型の範囲を示す機能をもつ型クラス制約というものがある。
foo :: Num n => n -> n
foo n = n + 5
うえの例では「Num n」のところが型クラス制約だ。言語拡張を使わない場合、型クラス制約は単純なかたちで使われる。
Foo bar
うえのような形で使い、型変数barを置き換える型が、型クラスFooのインスタンスである必要があることを示す。
タプルの3つの値を、型によって決められた順に整列する例
実用性はともかく、十分に単純で、意味がわかりやすい例として、タプルの3つの要素を型によって整列する例を挙げる。つぎのように使うことができる。
> tupleOrder ((), 'c', True) :: (Bool, Char, ())
(True,'c',())
> tupleOrder ((), 'c', True) :: (Char, (), Bool)
('c',(),True)
コードは、つぎのようになる。
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}
module TupleOrder where
class FromThree d a b c where
fromThree :: a -> b -> c -> d
instance {-# INCOHERENT #-} FromThree a a b c where
fromThree x _ _ = x
instance {-# INCOHERENT #-} FromThree b a b c where
fromThree _ y _ = y
instance {-# INCOHERENT #-} FromThree c a b c where
fromThree _ _ z = z
tupleOrder :: (FromThree t1 a b c, FromThree t2 a b c, FromThree t3 a b c) =>
(a, b, c) -> (t1, t2, t3)
tupleOrder (x, y, z) = (fromThree x y z, fromThree x y z, fromThree x y z)
ひとつめのインスタンス宣言は、結果の型が第1引数の型とおなじだった場合であり、関数fromThreeの返り値は第1引数になる。ふたつめのインスタンス宣言は、結果の型が第2引数の型とおなじだった場合であり、関数fromThreeの返り値は第2引数になる。みっつめのインスタンス宣言についても同様だ。
関数tupleOrderは、そのように定義されたfromThreeを利用することで、3つの値から、それぞれの型の値を選び出している。
FlexibleContextsが必要になるのは、関数tupleOrderの型クラス制約の部分だ。型クラスが型引数をみっつとる必要があるので、型クラス制約をフレキシブルにする必要がある。
まとめ
「使っている言語拡張はすべて説明する」という方針でやってきたのだけど、FlexibleContextsを単体で説明するのは、むずかしい。むずかしいというよりは、つまらないし、記事にならない。ということで、3要素タプルを型で整列するという多少キャッチーな例題を紹介した。