Freer Effectsが、だいたいわかった: 10. 存在型による拡張可能なデータ構造(Open Union)
目次
(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)
- 追加の言語拡張
- モナドを混ぜ合わせる(開いた型で)
- FreeモナドとOpen Unionを組み合わせる
- 状態モナドにエラーモナドを追加する
- Open Unionを型によって安全にする
- Freer Effectsで、IOモナドなどの、既存のモナドを使用する
- 関数を保管しておくデータ構造による効率化
- いろいろなEffect
- 関数handleRelayなどを作成する
- NonDetについて、など
はじめに
存在型を使用した開かれた直和型について説明する。 存在型(ExistentialQuantification拡張)の解説も参照のこと。
いろいろな型の値を含むリスト
たとえば、Haskellでは、つぎのようなリストを定義することはできない。
[123, True, (), 'c']
こういうことをしたければ、つぎのようなデータ型を作る必要がある。
data Value
= Unit ()
| Bool Bool
| Integer Integer
| Char Char
このように定義しておけば、つぎのようなリストを定義することができる。
[Integer 123, Bool True, Unit (), Char 'c']
リストに含む型がはじめからわかっていれば、これでいい。それがあらかじめわかっていないとき、開かれた直和型が必要になる。
{-# LANGUAGE ExistentialQuantification, GADTs #-}
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}
import Unsafe.Coerce
data UnionValue = forall x . UnionValue x
hetero :: [UnionValue]
hetero = [
UnionValue (123 :: Integer), UnionValue True,
UnionValue (), UnionValue 'c' ]
fromHetero :: [UnionValue] -> (Integer, Bool, (), Char)
fromHetero [UnionValue n, UnionValue b, UnionValue u, UnionValue c] = (
unsafeCoerce n, unsafeCoerce b, unsafeCoerce u, unsafeCoerce c )
対話環境で試してみよう。
> :load openUnion.hs
> fromHetero hetero
(123,True,(),'c')
このデータ型であれば、あとから「値構築子を追加するのとおなじこと」ができる。
doubleValue :: UnionValue
doubleValue = UnionValue (123 :: Double)
これは、つぎのようにデータ型Valueに値構築子を追加したようなものである。
data Value
= Unit ()
| Bool Bool
| Integer Integer
| Char Char
| Double Double
追加できる文脈
ここで、モナドを混ぜ合わせる(閉じた型で)でみた、データ型SEについて考える。
data SE s e a where
Get :: SE s e s
Put :: s -> SE s e ()
Exc :: e -> SE s e a
これに、Wrierモナドの機能を追加して、つぎのようなデータ型とした。
data SE s e w a where
Get :: SE s e w s
Put :: s -> SE s e w ()
Exc :: e -> SE s e w a
Writer :: w -> SE s e w ()
おなじことを開いた直和型を使って実装してみよう。ここで、「開かれてい」てほしいのは機能または文脈であり、型変数aであらわされる型の値である、かえされる値については、開かれている必要はないことに注意する。
data Union a = forall t . Union (t a)
まずは、状態モナドの機能と、エラーモナドの機能とを、この型でまとめてみよう。
data State s a where
Get :: State s s
Put :: s -> State s ()
data Exc e a where
Exc :: e -> Exc e a
deriving Show
effects :: [Union ()]
effects = [Union $ Put (123 :: Integer), Union $ Exc "hello"]
データ型Excでだけ、deriving Showとしているのは、「試してみる都合上」だ。さらにWriterモナドの機能を追加してみる。データ型Writerを定義して、サンプルのリストeffectsを編集する。
data Writer w a where
Writer :: w -> Writer w ()
effects :: [Union ()]
effects = [
Union $ Put (123 :: Integer),
Union $ Exc "hello",
Union $ Writer "world" ]
fromUnion :: Union a -> t a
fromUnion (Union tx) = unsafeCoerce tx
Union型の値から、なかみを取り出す関数fromUnionも定義した。型エラーなどなく読み込めることを確認する。
> :load openUnion.hs
> :type effects
effects :: [Union ()]
> fromUnion $ effects !! 1 :: Exc String ()
Exc "hello"
データ型State、Exc、Writerのみっつを、おなじデータ構造に格納することができ、かつ、型がわかっていれば、そこから取り出せるということがわかった。
まとめ
存在型を使って、開かれた直和型を作ることができる。まずは、ふつうの開かれた型を作り、それから、「文脈についてだけ」開かれた直和型を作ってみた。