22. 数値型クラス
class (Eq a, Show a) => Num a where
(+), (-), (*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
x - y = x + negate y
negate x = 0 - x
• 数値としての特徴を持つ型のクラス
• 加減乗除、反転、絶対値、数字からの変換など
• Num である為には Eq, Show でもある必要がある
• 型クラスは、型への「制約」「述語」として考えられる
23. ファンクタ型クラス
class Functor f where
fmap :: (a -> b) -> f a -> f b
• 普通の函数を「持ち上げ」られるコンテナのよう
なもの
• []、Maybe、IO などなど
• Maybe a は値を持つが、 Maybe は値を持たない
• Maybe :: * → * :型を受けとって型を返す「函
数」みたいなもの
24. 多引数型クラスの例
class Monad m => MonadState s m | m -> s where
get :: m s
get = state (s -> (s, s))
put :: s -> m ()
put s = state (_ -> ((), s))
state :: (s -> (a, s)) -> m a
state f = do
s <- get
let ~(a, s) = f s
put s
return a
• 「m は内部状態 s を持つモナド」
• (モナド=すごいコンテナ)
• m を決めると s が決まる:「m → s」関数従属性
27. 例1: MonadStateの書き換え
class Monad m => MonadState m where
type State m :: *
get :: m (State m)
get = state (s -> (s, s))
put :: State m -> m ()
put s = state (_ -> ((), s))
state :: (State m -> (a, State m)) -> m a
state f = do
s <- get
let ~(a, s) = f s
put s
return a
• State m が今までの s
• 型名がドキュメントの役割を果している
• 複雑なFunDepsを無くし、かつ一引数クラスで書ける
28. 例2:演算キャスト
class Add a b where
type SumType a b
add :: a -> b -> SumType a b
instance Add Int Int where
type SumType Int Int = Int
add = (+)
instance Add Int Double where
type SumType Int Double = Double
a `add` b = fromIntegral a + b
• 二つの型の足し算を定義
• SumType はキャストされた結果の型
29. 例3:マップの一般化
lass Key k where
data Map k :: * -> *
empty :: Map k v
lookup :: k -> Map k v -> Maybe v
insert :: k -> v -> Map k v -> Map k v
instance Key Int where
newtype Map Int a = IMap (IM.IntMap a)
empty = IMap IM.empty
lookup k (IMap d) = IM.lookup k d
insert k v (IMap d) = IMap $ IM.insert k v d
instance Key ByteString where
newtype Map ByteString a = BSMap (Trie a)
instance (Key a, Key b) => Key (a, b) where
newtype Map (a, b) v = TMap (Map a (Map b v))
• 辞書構造をキーの型によって使いわけ
36. 例:型付き構文木
data Expr a where
Zero :: Expr Int
Succ :: Expr Int -> Expr Int
Plus :: Expr Int -> Expr Int -> Expr Int
Yes :: Expr Bool
No :: Expr Bool
IsZero :: Expr (Int -> Bool)
Apply :: Expr (a -> b) -> Expr a -> Expr b
eval :: Expr a -> Expr a
eval (Succ a) = Succ (eval a)
eval (Plus a b) = eval a
eval (Plus a (eval -> Succ b)) = Succ (eval $ Plus a b)
eval (Apply IsZero (eval -> Zero)) = Yes
eval (Apply IsZero (eval -> Succ _)) = No
eval a = a
• 各コンストラクタに型が付加されている
• 変な構文木を弾いてくれる!
40. ST Monad
• State thread: メモリを用いた高速な状態
更新をするための機構
• Haskell は pure なので外に inpure な操作
が漏れないようにしたい
41. ST Monad
data ST s a
-- 内部状態 s を持ち、a の値を返す演算。
data STRef s a
-- 内部状態 s を持ち、a の値を保持する可変値。
-- これも s を漏らしてはいけない。
newSTRef :: a -> ST s (STRef s a)
-- 新しい STRef を作る。その際 ST と STRef の内部状態は統一する。
readSTRef :: STRef s a -> ST s a
writeSTRef :: STRef s a -> a -> ST s ()
modifySTRef :: STRef s a -> (a -> a) -> ST s ()
-- STRef を読み書きするための函数。ST内部でしか使えない。
runST :: (forall s. ST s a) -> a
-- ST 演算を実行して、結果を返す函数。
-- 内部状態は捨てられるので純粋性は保たれる。
42. 利用例
f str = do
ref <- newSTRef str
modifySTRef ref (++ ", that is the value")
readSTRef ref
main :: IO ()
main = do
print $ runST $ f "hoge"
• 簡単な例
• 破壊的代入を行えるので、STArray など
高速な配列演算を行うための型もある
• Bloom Filter の実装などにも使われる
43. 外部に漏れない?
leaker str = do
ref <- newSTRef str
modifySTRef ref (++ ", that is the value")
return ref
main :: IO ()
main = do
let ref = runST $ leaker "hoge"
print $ runST $ anotherEvilSToperation ref
• STRef を外に返すような函数を書けば漏
れるんじゃないの?
44. コンパイルしてみる
/Users/hiromi/pfi/seminar-20120411/test01.lhs:121:11:
Couldn't match type `a0' with `STRef s [Char]'
because type variable `s' would escape its scope
This (rigid, skolem) type variable is bound by
a type expected by the context: ST s a0
In the second argument of `($)', namely `leaker "hoge"'
In the second argument of `($)', namely `runST $ leaker "hoge"'
In the expression: print $ runST $ leaker "hoge"
Failed, modules loaded: none.
• STRef s [Char] の s は ST の内部状態と同じ
• s は runST :: (forall s. ST s a) → a の引数内側で束縛
• 何でもいい、だからこそ何だかわからない!
45. ちょっと理由説明
• runST :: (forall s. ST s (STRef s a))
→ forall s. STRef s a
• と云う型じゃだめなの?
• 束縛変数の記号を変えてもいいので……
• (forall s’. ST s’ (STRef s’ a)) → forall s. STRef s a
• と書き換えることも出来て、型があわない
48. 実装
newtype RIO s a = RIO (ReaderT (IORef [Handle]) IO a)
deriving (Functor, Monad, MonadReader (IORef [Handle]))
newtype RHandle s = RHandle Handle
runRIO :: (forall s. RIO s a) -> IO a
runRIO (RIO act) = bracket (newIORef []) (readIORef >=> mapM_ hClose) $
runReaderT act
openFileR :: FilePath -> IOMode -> RIO s (RHandle s)
openFileR fp mode = do
h <- RIO $ liftIO $ openFile fp mode
RIO $ ReaderT $ ref -> liftIO $ modifyIORef ref (h:)
return (RHandle h)
• 開いたハンドルへの参照を持っておく
• 終了時に解放するようにする
• ハンドルは全てRHandle の形で扱い、閉じれない
49. 使ってみる
test = do
h <- openFileR "conf.txt" ReadMode
str <- hGetLineR h
putStrLnR str
main = runRIO test
• 型は省略しても推論してくれる(!!)
• 上の実装だと一リージョンしか扱えな
いが、型クラスによってネスト出来る
51. やりたい例
1 ファイル1を開く h1 :: RHandle s RIO s IO
RIO t (RIO s IO)
52. やりたい例
1 ファイル1を開く h1 :: RHandle s RIO s IO
RIO t (RIO s IO)
53. やりたい例
1 ファイル1を開く h1 :: RHandle s RIO s IO
RIO t (RIO s IO)
h1 2 設定ファイルを開く
を渡す
54. やりたい例
1 ファイル1を開く h1 :: RHandle s RIO s IO
RIO t (RIO s IO)
h1 2 設定ファイルを開く
h2, hc :: RHandle t
を渡す
3 ファイル2の位置を読み、開く
55. やりたい例
1 ファイル1を開く h1 :: RHandle s RIO s IO
RIO t (RIO s IO)
h1 2 設定ファイルを開く
h2, hc :: RHandle t
を渡す
3 ファイル2の位置を読み、開く
4 ファイル3を(親リージョンで)開く h3 :: RHandle s
56. やりたい例
1 ファイル1を開く h1 :: RHandle s RIO s IO
RIO t (RIO s IO)
h1 2 設定ファイルを開く
h2, hc :: RHandle t
を渡す
3 ファイル2の位置を読み、開く
4 ファイル3を(親リージョンで)開く h3 :: RHandle s
5 1, 2の内容を交互に3へ書き込む
57. やりたい例
1 ファイル1を開く h1 :: RHandle s RIO s IO
RIO t (RIO s IO)
h1 2 設定ファイルを開く
h2, hc :: RHandle t
を渡す
3 ファイル2の位置を読み、開く
4 ファイル3を(親リージョンで)開く h3 :: RHandle s
5 1, 2の内容を交互に3へ書き込む
h2, hc を解放
h3 を返す; h3は型があうので返せる
58. やりたい例
1 ファイル1を開く h1 :: RHandle s RIO s IO
RIO t (RIO s IO)
h1 2 設定ファイルを開く
h2, hc :: RHandle t
を渡す
3 ファイル2の位置を読み、開く
4 ファイル3を(親リージョンで)開く h3 :: RHandle s
5 1, 2の内容を交互に3へ書き込む
h2, hc を解放
h3 を返す; h3は型があうので返せる
6 1 に残りがあれば それを 3へ書き込む
59. やりたい例
1 ファイル1を開く h1 :: RHandle s RIO s IO
RIO t (RIO s IO)
h1 2 設定ファイルを開く
h2, hc :: RHandle t
を渡す
3 ファイル2の位置を読み、開く
4 ファイル3を(親リージョンで)開く h3 :: RHandle s
5 1, 2の内容を交互に3へ書き込む
h2, hc を解放
h3 を返す; h3は型があうので返せる
6 1 に残りがあれば それを 3へ書き込む
h3, h1 を解放
62. Session 型の例
typecheck1 $ c -> send c "abc"
:: (Length ss l) => Session t (ss :> Send [Char] a) (ss :> a) ()
example from [10]
• 操作が 型レベルリストで並んでいる
• Session t (事前条件) (事後条件) 結果
• 上の例は「一度だけ、一つだけチャネ
ルを使って文字列をを送る」と読む
• t は Rank 2 多相によるリーク防止
63. 昇進──Data Kinds
• Data Kinds?
• データ型を型レベルへ「持ち上げ」
• GADTs や Type Family などと組み合わ
せて強力な威力を発揮する
66. 実装例
data Nat = Z | S Nat
data Vector :: Nat -> * -> * where
Nil :: Vector Z a
Cons :: a -> Vector n a -> Vector (S n) a
vhead :: Vector (S n) a -> a
vhead (Cons a _) = a
vtail :: Vector (S n) a -> Vector n a
vtail (Cons _ as) = as
vnull :: Vector n a -> Bool
vnull Nil = True
vnull _ = False
type family (a :: Nat) :+: (b :: Nat) :: Nat
type instance Z :+: a = a
type instance S n :+: a = S (n :+: a)
vappend :: Vector n a -> Vector m a -> Vector (n :+: m) a
vappend Nil v = v
vappend (Cons a as) v = Cons a (as `vappend` v)
70. References
1. “Fun with type functions”, Oleg Kiselyov, Simon Peyton Jones and Chung-chieh
Shan, 2010
2. “Giving Haskell a Promotion”,B. A.Yorgey, S. Weirich, J. Cretin, Simon Peyton
Jones and D.Vytiniotis, 2011
3. “Lightweight Monadic Regions”, Oleg Kiselyov, Chung-chieh Shan, 2008
4. “Algebra of Programming in Agda”, Shin-Cheng Mu, Hsiang-Shang Ko, Patrik
Jansson, 2009
5. “Yesod Web Framework for Haskell”
6. “データ型 - Wikipedia”, Wikipedia
7. “Type safety - Wikipedia”, Wikipedia
8. “Phantom type - HaskellWiki”, HaskellWiki
9. “Region-based resource management”, Oleg Kiselyov, 2011
10. “full-sessions-0.6.1”, id:keigoi
11. “プログラミングCoq”, 池渕未来
&#x6700;&#x65B0;&#x7248; GHC &#x304B;&#x3089; Num &#x306F; Eq &#x3082; Show &#x3082;&#x8981;&#x6C42;&#x3057;&#x306A;&#x304F;&#x306A;&#x308A;&#x307E;&#x3057;&#x305F;&#x3002;\n
&#x6700;&#x65B0;&#x7248; GHC &#x304B;&#x3089; Num &#x306F; Eq &#x3082; Show &#x3082;&#x8981;&#x6C42;&#x3057;&#x306A;&#x304F;&#x306A;&#x308A;&#x307E;&#x3057;&#x305F;&#x3002;\n