───When You Will Try Haskell
これから Haskell を書くにあたって
目次
3頁 … はじめに
9頁 … GHC 7.8 からの変更点
69頁 … Haskell が遅いと言われるワケとか
106頁 … 知らないと損する言語拡張たち
150頁 … FFI の話
161頁 … おまけ
はじめに
Who Am I?
twitter: func(@func_hs)
Haskell使いだったり、Clojure使いだったり。
現在求職中
最近E本ゲットしたからErlangも勉強する(その内)
はじめに
本発表の目的
Haskell には意外と落とし穴がある。
はじめに
本発表の目的
Haskell には意外と落とし穴がある。
✔ 計算量(オーダ)
はじめに
本発表の目的
Haskell には意外と落とし穴がある。
✔ 計算量(オーダ)
✔ メモリ使用量
はじめに
本発表の目的
Haskell には意外と落とし穴がある。
✔ 計算量(オーダ)
✔ メモリ使用量
✔ 言語仕様や処理系の実装のあれこれ
はじめに
本発表の目的
Haskell には意外と落とし穴がある。
✔ 計算量(オーダ)
✔ メモリ使用量
✔ 言語仕様や処理系の実装のあれこれ
これらへの注意の仕方と対処法を知る
GHC 7.8 からの変更点
10頁 … Monad Proposal
22頁 … Foldable
43頁 … Traversable
57頁 … Prelude の関数の変化
63頁 … 変更後の標準の型クラス全体図
67頁 … OpenGL から見る実装例
Monad Proposal
正確には Functor-Applicative-Monad Proposal
Monad のリファクタリングの提案
Monad は正式に Applicative の子型クラスに
話題自体は2014年よりも前から始まっていた。
最初期の Haskell では繋がっていたらしい。しかし…
親型クラスのメソッドをデフォルトで実装する機能がなかった。
Monad Proposal
instance 宣言のおさらい
記法
* instance C1 T1 where ...
* instance C1 a => T1 a where ...
* instance C1 a => Cn (T1 a) where ...
型クラスの実装をデータ型に対して与える
Monad Proposal
型変数のないデータ型への実装
instance C1 T1 where
f x = ...
型クラスの型変数は書かない
Monad Proposal
型変数のあるデータ型への実装
instance C1 a => T1 a where
f x = ...
instance (C1 a, C2 a, ...) => T1 a where
f x = ...
インスタンスを与える型変数を明示する
Monad Proposal
特定のインスタンスを持つデータ型への実装
instance C1 a => Cn (T1 a) where
f x = ...
instance (C1 a, C2 a,...) => Cn (T1 a) where
f x = ...
特定する型クラスも明示する
Monad Proposal
自動で実装できる型クラス
➜ deriving 句で導出できる型クラス
Eq, Ord, Enum, Bounded, Read, Show
コンストラクタの並び等から自明に導ける。
(もちろん自前で書いてもよい)
Monad Proposal
自動で実装できる型クラス
✔ 自明で導くことができる
✔ その型自身が直接依存する型クラス
に限る
Monad Proposal
Monad と Applicative の実装は?
❌自明では導くことができない
これら 2 つはコンテナの中身をどう扱うかの話
❌Applicative は Monad の親クラス
Monad のインスタンスを持つ型が直接依存しているわけではない。
Monad Proposal
公式の対応
return = pure
Monad の return の実装を Applicative の pure にリファクタリング
<*> = ap
コンテナから値を取り出し関数を適用するという点で同義
Monad Proposal
公式の対応
* pure :: a -> f a
* return :: a -> m a
* <*> :: f (a -> b) -> f a -> f b
* ap :: m (a -> b) -> m a -> m b
コンテキストが変わるだけ
Monad Proposal
公式の対応
Functor と Applicative の実装
➜ サードパーティが各自実装する形に
利便性より理論を優先?
Monad Proposal
Monad Proposal まとめ
✔ 2014年(GHC 7.8)以前に実装したモナドは
✔ リファクタリングしなきゃ(使命感)
Foldable
実装のイメージ
x1 x2 x3 x4 x5 …………………………… xN
f
?
コンテナの要素を一つに畳む
Foldable
期待される性質
✔ foldr f z t =
appEndo (foldMap (Endo . f) t) z
✔ foldl f z t =
appEndo (getDual (Dual . Endo . flip f) t)) z
✔ fold =
foldMap id
Foldable
期待される性質
Endo / appEndo
Dual / getDual
どちらも Data.Monoid モジュールにある
データ型
Foldable
期待される性質
newtype Endo a = Endo {appEndo :: a -> a}
モノイドのコンテキストで関数を合成する
Foldable
期待される性質
newtype Endo a = Endo {appEndo :: a -> a}
➜ instance Monoid (Endo a) where
mempty = Endo id
Endo f `mappend` Endo g = Endo (f . g)
Foldable
期待される性質
newtype Dual a = Dual {getDual :: a}
値を合成させるためのコンテナ
Foldable
期待される性質
newtype Dual a = Dual {getDual :: a}
➜ instance Monoid a => Monoid (Dual a)
where
mempty = Dual mempty
Dual x `mappend` Dual y =
Dual (x `mappned` y)
Foldable
Monoid に期待される性質
✔ mappend mempty x = x
✔ mappend x mempty = x
✔ mappend x (mappend y z) =
mappend (mappend x y) z
✔ mconcat = foldr mappend mempty
Foldable
Monoid に期待される性質
mappend mempty x = x
mappend x mempty = x
左単位元と右単位元の保証
Foldable
Monoid に期待される性質
mappend x (mappend y z) =
mappend (mappend x y) z
結合則の保証
Foldable
Monoid に期待される性質
mconcat = foldr mappend mempty
連結した結果が正しく閉じていることの保証
Foldable
Monoid に期待される性質
この内、必ず実装が必要なのは…
✔ mempty
✔ mappend
の 2 つ
Foldable
Monoid に期待される性質
mconcat にはデフォルト実装がある
➜ 特に必要なければ改めて実装しなくてよい
Foldable
話を戻して Foldable の話
foldr f z t =
appEndo (foldMap (Endo . f) t) z
foldl f z t =
appEndo (getDual (Dual . Endo . flip f) t)) z
要素の畳み方を定義する必要がある
Foldable
話を戻して Foldable の話
✔ foldMap
✔ foldr
最低限実装が必要なのはこの 2 つの内
少なくとも 1 つ
Foldable
話を戻して Foldable の話
✔ foldMap
✔ foldr
双方にデフォルト実装がある
➜ 片方が決まればもう片方も自動的に決まる
Foldable
話を戻して Foldable の話
foldMap :: Monoid m => (a -> m) -> t a -> m
foldMap f = foldr (mappend . f) mempty
Foldable のインスタンスを持つコンテナの要素を
Monoid に型変換させる
Foldable
話を戻して Foldable の話
foldr :: (a -> b -> b) -> b -> t a -> b
foldr f z t = appEndo (foldMap (Endo #. f) t) z
コンテナの中身をモノイドのコンテキストに
乗せながら畳んでいく
Foldable
話を戻して Foldable の話
foldr :: (a -> b -> b) -> b -> t a -> b
foldr f z t = appEndo (foldMap (Endo #. f) t) z
(´・_・`).oO((#.) ってなんだ?)
Foldable
話を戻して Foldable の話
(#.) :: (b -> c) -> (a -> b) -> (a -> c)
(#.) _f = coerce
coerce :: Coercible * a b => a -> b
coerce = let x = x in x
安全かつ強制的に型変換させるための関数
Foldable
Foldable まとめ
✔ 要素が Monoid のインスタンスであれば
どんな値も畳むことができる
✔ Endo は関数合成用のコンテナ
Dual は値合成用のコンテナ
✔ Monoid 用のデータ型は他にもあるよ!
Traversable
実装のイメージ
コンテナの要素に関数を適用し、入れ直す
x1 x2 x3 x4 x5 xN……………………………
f
y1 y2 y3 y4 y5 yN……………………………
Traversable
最低限実装が必要なメソッド
✔ traverse
✔ sequenceA
この 2 つの内いずれか 1 つ
双方にデフォルト実装がある。以下省略
Traversable
最低限実装が必要なメソッド
traverse
:: Applicative f => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f
コンテナの要素を走査しながら関数を適用
していく
Traversable
最低限実装が必要なメソッド
traverse
:: Applicative f => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f
コンテナが入れ替わることに注意
Traversable
最低限実装が必要なメソッド
sequenceA :: Applicative f => t (f a) -> f (t a)
sequenceA = traverse id
走査するだけ
(中の値自体には変更を加えない)
Traversable
最低限実装が必要なメソッド
sequenceA :: Applicative f => t (f a) -> f (t a)
sequenceA = traverse id
コンテナが入れ替わることに注意
(大事なことなのでry)
Traversable
最低限実装が必要なメソッド
コンテナを入れ替える理由
✔ 無駄なネストを除去し、
✔ 型の表現を簡潔にするため
(型注釈も込みで)
実装次第でより深いネストも走査できる
Traversable
最低限実装が必要なメソッド
コンテナを入れ替える理由
✔ 無駄なネストを除去し、
✔ 型の表現を簡潔にするため
(型注釈も込みで)
ただし注意が必要
Traversable
期待される性質
✔ t . traverse f =
traverse (t . f)
✔ traverse Identity =
Identity
✔ traverse (Compose . fmap g . f) =
Compose . fmap (traverse g) . traverse f
Traversable
期待される性質
✔ t . sequenceA =
sequenceA . fmap t
✔ sequenceA . fmap Identity =
Identity
✔ sequenceA . fmap Compose =
Compose . fmap sequenceA . sequenceA
Traversable
期待される性質
✔ t . traverse f = traverse (t . f)
✔ t . sequenceA = sequenceA . fmap t
関数合成の自然性の保証
(関数合成の効率化)
Traversable
期待される性質
✔ traverse Identity = Identity
✔ sequenceA . fmap Identity = Identity
同一性の保証
(id がちゃんとそのままの値を返すこと)
Traversable
期待される性質
✔ traverse (Compose . fmap g . f) =
Compose . fmap (traverse g) . traverse f
✔ sequenceA . fmap Compose =
Compose . fmap sequenceA . sequenceA
結合則の保証
Traversable
Traversable まとめ
✔ コンテナの要素を走査し、更新する
✔ その際にコンテナの構造を均して簡潔化する
✔ 中の要素は Applicative の実装が必要
(更新してコンテナに再適用させるため)
Prelude の関数の変化
意外とリファクタリングされていた
コンテナを畳むか走査するかで二分された
Prelude の関数の変化
意外とリファクタリングされていた
Foldableに所属
null, length, elem, notElem, maximum,
minimum, sum, product, and, or, any, all,
concat, concatMap, mapM_(forM_), sequence_
Prelude の関数の変化
意外とリファクタリングされていた
Traversableに所属
mapM(forM), sequence
Prelude の関数の変化
意外とリファクタリングされていた
mapM(forM)
コンテナ(変換結果)を返す
mapM_(forM_)
コンテナを返さない(命令の実行だけ)
Prelude の関数の変化
意外とリファクタリングされていた
sequence
結果を返すモナドなコンテナを評価する
sequence_
結果を返さないモナドなコンテナを評価する
Prelude の関数の変化
意外とリファクタリングされていた
sequence
評価して実行した結果をコンテナに詰め直す
sequence_
評価して実行するだけ(Unit型 `()` を返す)
変更後の標準の型クラス全体図
正式に養子縁組されました。
実線矢印が直接の継承関係
変更後の標準の型クラス全体図
正式に養子縁組されました。
点線矢印が挙動が類似している型クラス
変更後の標準の型クラス全体図
正式に養子縁組されました。
太線矢印(ArrowApply)は中の要素が Monad
変更後の標準の型クラス全体図
正式に養子縁組されました。
引用: Typeclassopedia
OpenGL から見る実装例
Foldable と Traversable はどう実装されているか
instance Foldable TexCoord4
foldr f a (TexCoord4 x y z w) =
x `f` (y `f` (z `f` (w `f` a)))
foldl f a (TexCoord4 x y z w) =
((((a `f` x) `f` y) `f` z) `f` w)
テクスチャ座標の畳込み等(一部抜粋)
OpenGL から見る実装例
Foldable と Traversable はどう実装されているか
instance Traversable TexCoord4 where
traverse f (TexCoord4 x y z w) =
pure TexCoord4 <*> f x <*> f y <*> f z
<*> f w
座標変換等(一部抜粋)
Haskell が遅いと言われるワケとか
70頁 … 評価しないものは溜まる
79頁 … データコンストラクタの中身
81頁 … 文字列の計算量とメモリ使用量
85頁 … タプル
88頁 … 自作データ型の高速化
97頁 … 型クラスの仕組み
102頁 … 競技プログラミングでの Haskell
評価しないものは溜まる
評価予定の値もスタックに
関数呼び出し中の未評価な値もスタックに乗る
➜ 評価されるまでは
評価しないものは溜まる
評価予定の値もスタックに
よくある階乗の計算
fact 0 = 1
fact n = n * fact (n - 1)
この n は何の問題もないように見えるが…
評価しないものは溜まる
評価予定の値もスタックに
よくある階乗の計算
fact 0 = 1
fact n = n * fact n
実は未評価なまま次の計算に渡されている
(評価されるのはパターンマッチの瞬間である)
評価しないものは溜まる
評価予定の値もスタックに
よくある階乗の計算
fact 0 = 1
fact n = n * fact n
引数部に名前だけの場合、それはパターンマッチ
されないことに注意。
評価しないものは溜まる
評価予定の値もスタックに
呼び出し回数が少ない内は無事に動く
➜ スタックもそれほど溜まらない
評価しないものは溜まる
評価予定の値もスタックに
しかし呼び出す回数が増えるにつれて、スタックは
どんどん溜まっていき、いずれは溢れてしまう
➜ 渡された値も未評価のまま…
この現象をスペースリークという
評価しないものは溜まる
評価予定の値もスタックに
対処法
✔ 他の関数に渡す前に強制的に評価させる
➜ seq 関数(この節で説明)
➜ BangPatterns(148頁)
評価しないものは溜まる
その場で評価させる方法
seq 関数
渡された値をその場で評価する(評価するだけ)
seq :: a -> b -> b
seq = let x = x in x
評価しないものは溜まる
その場で評価させる方法
階乗の例に適用してみる
fact 0 = 1
fact n = n `seq` n * fact (n - 1)
これにより、 n は seq 関数に渡され、その場で評
価されるようになった
データコンストラクタの中身
値がそのまま入るわけではない
Maybe Intの場合
Nothing Just
I# Int#
or P
P: ポインタ
データコンストラクタの中身
値がそのまま入るわけではない
Maybe Intの場合
Nothing Just
I# Int#
or P
P: ポインタ
実際の Int 型の値
文字列の計算量とメモリ使用量
計算量多そう…
: P
"Hello"(UTF-8)
P P
C# Char#
'H'
P P
C# Char#
'e'
P
C# Char#
'l'
P P P
C# Char#
'l'
P : : : :
文字列の計算量とメモリ使用量
計算量多い(確信)
"Hello"(UTF-8)
: P P P
C# Char#
'H'
P P
C# Char#
'e'
P
C# Char#
'l'
P P P
C# Char#
'l'
P : : : :
⑲
⑱
⑰⑯
⑮⑩⑤
⑭
⑬
⑫⑪
⑨
⑧
⑦⑥
③
②① ㉒㉑ ㉓
④
⑳
1 文字だけでも 4 回以上の計算が…
文字列の計算量とメモリ使用量
メモリ食いすぎィ!
構築子: 1 word
型引数: 1 word / arg
文字列 1 文字分 = 5 words
構築子(:) + ポインタ * 2 + Char のサイズ
1 word 2 words 2 words
C# + Char#
※32bit: 20 bytes, 64bit: 40 bytes
文字列の計算量とメモリ使用量
文字列効率化の試み
ライブラリレベル
➜ bytestring
➜ text
処理系レベル
➜ OverloadedStrings(98頁)
タプル
タプルも例外ではない
(,)
(Int, Int)
P P
I# Int# I# Int#
(Int, Int, Int)
(,) P P
I# Int#
P
I# Int#
I# Int#
タプル
タプルも例外ではない
(Maybe Int, Maybe Int)
(,) P P
Nothing or Just P
Nothing or Just P
I# Int#
I# Int#
タプル
タプルの高速化
➜ UnboxedTuples(108頁)
自作データ型の高速化
正格性フラグ
data T = T !Int
フラグ(!)の付いた型引数は正格評価される
✔ 受け取ったその場で評価
✔ 遅延評価ならではのデメリットは解消される
自作データ型の高速化
正格性フラグ
data T = T !Int
フラグ(!)の付いた型引数は正格評価される
❌ その値はデータ生成時に直ちに
渡されなければならない
❌ その型を部分適用できなくなる
自作データ型の高速化
正格性フラグ
関数に付与する場合
➜ BangPatterns(148頁)
自作データ型の高速化
標準にあるハッシュ付きの型は Unpack 可能
Int, Integer, Float, Double, Char, Word[N],
Int[N]
これらはライブラリではただの Unpack 可能な型
➜ プリミティブな値は処理系が保持
自作データ型の高速化
標準にあるハッシュ付きの型は Unpack 可能
data T a = T {-# Unpack #-} !Int
T Int#
プリミティブな値がそのまま型引数に収まる
✔ 計算量とメモリ消費量が減る
✔ 生の値なので遅延評価不可
(正格性フラグ必須)
ただのバイナリ
自作データ型の高速化
標準にあるハッシュ付きの型は Unpack 可能
data T a = T {-# Unpack #-} !Int
T Int#
プリミティブな値がそのまま型引数に収まる
❌ 構築子が 1 つだけの型に限る
❌ 通常の型と同じようには使えない
ただのバイナリ
自作データ型の高速化
標準にあるハッシュ付きの型は Unpack 可能
data T a = T {-# Unpack #-} !Int
T Int#
プリミティブな値がそのまま型引数に収まる
❌ 複数あると処理系が判断できない
❌ 通常の型はポインタであると期待される
ただのバイナリ
自作データ型の高速化
標準にあるハッシュ付きの型は Unpack 可能
data T a = T {-# Unpack #-} !Int
T Int#
プリミティブな値がそのまま型引数に収まる
❌ 複数あると処理系が判断できない
❌ 許してしまうと GC の対象になり、
厄介なバグを生みやすくなる
ただのバイナリ
自作データ型の高速化
標準にあるハッシュ付きの型は Unpack 可能
data T a = T {-# Unpack #-} !Int
T Int#
プリミティブな値がそのまま型引数に収まる
❌ 複数あると処理系が判断できない
⚠ 不本意な再ボックス化を防ぐために
最適化オプションを付けるべき
ただのバイナリ
型クラスの仕組み
その正体は辞書
型クラスへの依存(もとい制約)情報は
辞書型のデータ構造によって管理される
型クラスの仕組み
その正体は辞書
:: C a => a
1. C a という制約が導入されて、
2. C a の情報を持つ辞書に型 a の値が適用される
型クラスの仕組み
その正体は辞書
:: C a => a
⚠ 型変数は、注釈中のいずれかのデータ型に
必ず到達できなければならない
型クラスの仕組み
その正体は辞書
:: C a => Hoge ❌
➜ C a だけではインスタンスを特定できない
型クラスの仕組み
その正体は辞書
⚠ インタプリタ(一般に GHCi)では遅い
➜ 制約の辞書も逐次生成される
➜ コンパイル、しよう(提案)
競技プログラミングでの Haskell
速度にシビアな問題では死ぬ
✔ 主に TLE で。
➜ 標準の文字列や List 等では間に合わない
問題がある
➜ それを埋め合わせるライブラリが使えない
➜ そもそも型の構造的に辛い…
競技プログラミングでの Haskell
速度にシビアな問題では死ぬ
対策
✔ 言語拡張を導入(詳細は後述)
➜ UnboxedTuples
➜ OverloadedStrings(※)
➜ OverloadedLists(※)
➜ BangPatterns
競技プログラミングでの Haskell
速度にシビアな問題では死ぬ
※のある拡張は競プロでは使用不可
bytestring も text も vector も競プロの
サイト側では用意されていない…
競技プログラミングでの Haskell
Haskell が使える競プロサービス
AtCoder
✔ GHC 7.8 以降
Codeforces
✔ GHC 7.8 以降
他
❌ (GHC がインストールされて)ないです…
知らないと損する言語拡張たち
107頁 … リテラルを置き換える
117頁 … 計算量を削減する
120頁 … データ型の表現力を向上する
122頁 … 型クラスの表現力を向上する
148頁 … 評価戦力を切り替える
リテラルを置き換える
Overloaded Strings
✔ 文字列(文字のリスト) を IsString の
インスタンスを持つ別の何かに昇格
例) ByteString, Text
IsString さえ実装されていればどんな型でもよい
リテラルを置き換える
Overloaded Strings
ByteString の例
instance IsString ByteString where
fromString = packChars
文字列を packChars に丸投げする
リテラルを置き換える
Overloaded Strings
Text の例
instance IsString Text where
fromString = pack
文字列を pack に丸投げする
リテラルを置き換える
Overloaded Strings
Data.ByteString を import してると
"Hello" :: ByteString
Data.Text を import してると
"Hello" :: Text
リテラルを置き換える
Overloaded Lists
✔ 何らかの List を IsList のインスタンスを持つ
別の何かに昇格
例) vector
IsList さえ実装されていればどんな型でもよい
リテラルを置き換える
Overloaded Lists
Vector の例
instance Ext.IsList (Vector a) where
type Item (Vector a) = a
fromList = fromList
fromListN = fromListN
toList = toList
リテラルを置き換える
Overloaded Lists
Vector の例
type Item (Vector a) = a
Item (Vector a) という型の組み合わせを型 a
として扱わせる言語拡張(ここでは触れない)
See: 7.7 Type families - The User's Guide
リテラルを置き換える
Overloaded Lists
Vector の例
fromList = fromList
List を Vector.fromList に丸投げ
リテラルを置き換える
Overloaded Lists
Vector の例
fromList = fromListN
List を Vector.fromListN に丸投げ
(List の最初の N 要素だけを Vector に入れる)
リテラルを置き換える
Overloaded Lists
Vector の例
toList = toList
Vector を List に変換する
計算量を削減する
Unboxed Tuples
Tuple からタグ(ポインタ部分)を取り除く
➜ ただの限定的な連続領域になる
➜ ポインタを挟まず、要素がスタックや
レジスタに直接置かれる
➜ Unpack されていない要素は通常通り
遅延評価される
計算量を削減する
Unboxed Tuples
Tuple からタグ(ポインタ部分)を取り除く
❌ (多層的な)型や関数の引数には渡せない
➜ 型ではない。
➜ ポインタを持っていない
➜ 引数ではない部分でなら問題ない
(返り値にする時や case 式など)
計算量を削減する
Unboxed Tuples
(Int, Int)
(,) P P
I# Int# I# Int#
(# Int, Int #)
I# Int# I# Int#
連続した領域として
扱われる
データ型の表現力を向上する
GADTs
データコンストラクタにも関数と同じ型注釈を
使えるようにする
data T a = T1 | T2 a (T a)
➜ data T a where
T1 :: T a
T2 :: a -> T a -> T a
データ型の表現力を向上する
GADTs
データコンストラクタにも関数と同じ型注釈を使え
るようにする
✔ 構造がネストするコンストラクタも楽に
定義できるようになる
やったぜ。
型クラスの表現力を向上する
Multi Param Type Classes
型クラスに複数の型変数を許可する
➜ メソッドが複数の異なるインスタンスを
受け取れるようになる
class C a b where
f :: a -> b -> b
型クラスの表現力を向上する
Functional Dependencies
型変数の関係性を明示する
➜ ある型が決まれば別の型も一意に決まる と
いう(依存)関係
class C a b | a -> b where
型クラスの表現力を向上する
Flexible Instances
instance 宣言のルールを緩和する
➜ より柔軟にインスタンスを実装できる
型クラスの表現力を向上する
Flexible Instances
通常時のルール
✔ 1 つの型クラスにつき型変数は 1 つまで
✔ 型変数の重複は許されない
✔ 制約は型変数にのみ与えられる
✔ 実装は必ずしなければならない
型クラスの表現力を向上する
Flexible Instances
拡張導入後のルール
➜ 多変数型クラスの実装を与えてもよい
➜ 型変数は重複していてもよい
➜ 代数的データ型を混ぜてもよい
➜ 宣言のみであってもよい
型クラスの表現力を向上する
Flexible Instances
拡張導入後のルール
✔ instance C a b where ...
✔ instance C a a where ...
✔ instance C a Int where ...
✔ instance C a b (=> ...)
型クラスの表現力を向上する
Flexible Instances
拡張導入後のルール
✔ 型シノニムも宣言に書ける
data T a = T1 | T2 a (T a)
type TT = T Int
instance Eq TT where ...
型クラスの表現力を向上する
Flexible Instances
注意点
⚠ 柔軟化した分、処理系は実装を
特定しにくくなる
型クラスの表現力を向上する
Flexible Instances
例1
class C a b
instance C a Int
instance C Int b
a と b のどちらに Int があっても特定できてしまう
型クラスの表現力を向上する
Flexible Instances
例2
class C a b
instance C Int b
instance C Int [b]
多相型なので一番目の b にも List があり得る
型クラスの表現力を向上する
Flexible Contexts
制約の文法に FlexibleInstance と同じ緩和を適用
する
型クラスの表現力を向上する
Flexible Contexts
⚠ 型変数に言及しなくてよい注釈は存在しない
❌ f :: C a b => Int
❌ f :: C a b => a -> Int
型クラスの表現力を向上する
Flexible Contexts
✔ それ以外は Flexible Instances と同様に書ける
➜ f :: C a b => a -> b -> b
➜ f :: C a a => a -> a -> a
➜ f :: C a (T b) => a -> a -> T b
➜ f :: C a TT => a -> a -> TT
型クラスの表現力を向上する
Overlapping Instances
特定可能なインスタンスの定義が複数ある場合
に、特定先を 1 つに絞らせる
型クラスの表現力を向上する
Overlapping Instances
⚠ 最も特殊性の高い(※)インスタンスが
1 つ以上存在している必要がある。
(※具体的に型が決められていること)
型クラスの表現力を向上する
Overlapping Instances
次のような制約のある注釈を考える
:: C Int Int -> Int -> Int -> a
型クラスの表現力を向上する
Overlapping Instances
以下のインスタンスが定義されているとする
instance C a Int
instance C Int b
instance C Int Int
型クラスの表現力を向上する
Overlapping Instances
この内、最も特殊性が高いのは茶色の定義
instance C Int b
instance C a Int
instance C Int Int
型クラスの表現力を向上する
Overlapping Instances
よって、
:: C Int Int -> Int -> Int -> a
という制約に対して
instance C Int Int
の定義がマッチされる
型クラスの表現力を向上する
Overlapping Instances
⚠ 特殊性の高いインスタンスがひとつもないと
判断された場合はエラーになる
型クラスの表現力を向上する
Overlapping Instances
instance C Int b
instance C a Int
だけだと特定のしようがない…(エラー)
型クラスの表現力を向上する
Incoherent Instances
特殊性のあるインスタンスが 1 つも
見つからなかった場合に、最もパターンの近い
インスタンスを選ばせる
型クラスの表現力を向上する
Incoherent Instances
:: C Int Int -> Int -> Int -> a
という制約に最も適合するインスタンスは
instance C Int Int
だが、これがない場合は
型クラスの表現力を向上する
Incoherent Instances
instance C Int b
instance C a Int
のいずれかを選択させる
型クラスの表現力を向上する
Incoherent Instances
⚠ どのインスタンスが選ばれるのかは、制約を
与えられた関数が呼び出されるまで
わからない
➜ インスタンスの選択を遅延させている
➜ 同じ制約のつもりでも違うインスタンスを
選択される可能性がある
型クラスの表現力を向上する
Incoherent Instances
✔ 複数適合しうるインスタンスをなるべく
書かないようにすることが大事
評価戦略を切り替える
Bang Patterns
関数の引数にも正格性フラグを付与できるように
する
f !x = x * 2
この関数の引数 x は正格評価される
評価戦略を切り替える
Bang Patterns
⚠ 当然ながら遅延評価ではないので、部分適用は
できない
FFIの話
151頁 … 呼び出せるプログラミング言語
154頁 … 呼び出し方
156頁 … Haskell での型の扱い
158頁 … ByteString から見る実装例
160頁 … hmatrix から見る実装例
呼び出せるプログラミング言語
対応状況
C
✔ 標準で使える
C++
▲ extern C 必須
❌ class や template は使用不可
(C と共通する部分のみ使用可)
呼び出せるプログラミング言語
対応状況
Java
✔ java-bridge があるが…
➜ 最近更新されてない…
➜ JNI と闘う覚悟はあるか?
(俺はない(´・_・`))
呼び出せるプログラミング言語
対応状況
.NET
✔ hs-dotnet があるが…
➜ 最近更新されてない…
➜ 恐らく開発自体が止まっている…
呼び出し方
構文(import)
foreign import callconv impent var :: ftype
callconv :=
ccall | stdcall | cplusplus | jvm | dotnet
impent := [string]
(呼び出すヘッダの名前と関数の名前。スペース区切り。なくてもよい)
呼び出し方
構文(export)
foreign export callconv expent var :: ftype
callconv :=
ccall | stdcall | cplusplus | jvm | dotnet
expent := [string]
(呼び出すヘッダの名前と関数の名前。スペース区切り。なくてもよい)
Haskell での型の扱い
型対応表
Haskell → C C → Haskell
CInt HsInt
CFloat HsFloat
CDouble HsDouble
Bool HsBool
Haskell での型の扱い
型対応表
Haskell → C C → Haskell
Ptr a * p
(ポインタ)
FunPtr a int (*p)(int)
(関数ポインタ)
ByteString から見る実装例
C の型を操作するので unsafe
foreign import ccall unsafe "string.h memchr"
c_memchr :: Ptr Word8 -> CInt -> CSize
-> IO (Ptr Word8)
ByteString から見る実装例
C の型を操作するので unsafe
memchr :: Ptr Word8 -> Word8 -> CSize
-> IO (Ptr Word8)
memchr p w s =
c_memchr p (fromIntegral w) s
hmatrix から見る実装例
行列演算ライブラリ
foreign import ccall unsafe "sort_indexD"
c_sort_indexD
:: CV Double (CV CInt (IO CInt))
C と Haskell の間を往復するのでオーバーヘッドが
すごいらしい…
(まだ使ったことがないので詳しくはなんとも…)
おまけ
162頁 … あると便利な言語拡張たち
171頁 … CPP
あると便利な言語拡張たち
BinaryLiterals
二進数による数値表現を可能にする
0b1010 -> 10(10進数)
あると便利な言語拡張たち
LambdaCase
無名関数の要領で case 式を書ける
(case 式に渡すデータの名前を省略できる)
case
p1 -> e1
p2 -> e1
....
あると便利な言語拡張たち
MagicHash
リテラルや型コンストラクタに ハッシュ(#)を使う
ことを許可する
✔ ハッシュ(#)自体には特に意味はない
✔ 慣習的に内部にプリミティブな値を
持っている型に付与されている
あると便利な言語拡張たち
MagicHash
リテラルや型コンストラクタにハッシュ(#)を使う
ことを許可する
'x'# Char➜ #
"foo"# Addr➜ #
3# Int➜ #
3## Word➜ #
あると便利な言語拡張たち
ParallelListComp
複数のリストを同時に内包表記できる
[(x, y) | x <- xs | y <- ys]
これは
zip xs ys
と同じことをしている
あると便利な言語拡張たち
ParallelListComp
お馴染みフィボナッチ数列
fibs =
0 : 1 : [a + b | a <- fibs | b <- tail fibs]
これは
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
と同じ
あると便利な言語拡張たち
PatternGuards
引数のパターンマッチの柔軟性が増す
通常
f :: Int -> Bool
f x
| n <= x && x <= m = ...
| otherwise = ...
あると便利な言語拡張たち
PatternGuards
引数のパターンマッチの柔軟性が増す
複数の条件を同時に書ける
f :: Int -> Bool
f x
| n <= x && x <= m, x >= n * 100 = ...
| otherwise = ...
あると便利な言語拡張たち
PatternGuards
引数のパターンマッチの柔軟性が増す
これは(||)を適用してるのと同じ
f :: Int -> Bool
f x
| n <= x && x <= m || x >= n * 100 = ...
| otherwise = ...
CPP
実は C 由来のプリプロセッサを書ける
GHC の時のみそのコードが動くようにする
{-# LANGUAGE CPP #-}
#ifdef __GLASGOW_HASKELL__
-- Some Codes Here
#endif
CPP
実は C 由来のプリプロセッサを書ける
GHC の時のみそのコードが動くようにする
{-# LANGUAGE CPP #-}
#ifdef __GLASGOW_HASKELL__
-- Some Codes Here
#endif
CPP
実は C 由来のプリプロセッサを書ける
GHC の特定のバージョンでのみそのコードが動く
ようにする
{-# LANGUAGE CPP #-}
#if __GLASGOW_HASKELL__==780
-- Some Codes Here
#endif
おしまい
話したいことまだまだたくさん
❌ 時間足りない
❌ 話もどんどん複雑化
➜ またの機会に話そうと思う
(機会がなければその内 Qiita に投稿する)
おしまい
───See You Again!

これから Haskell を書くにあたって