───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 を書くにあたって