Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

2016/2/14 開催の第一回 hs.hs 勉強会で用いたスライドです。
流れは以下の通りです。
1. はじめに
 自己紹介や本発表の目的について
2. GHC 7.8 からの変更点
 GHC 7.8 の前後で起きたライブラリの変化と、その変化への対処法について
3. Haskell が遅いと言われるワケとか
 遅延評価や各種データ構造の内部表現に起因する諸問題と、その対処法について
4. 知らないと損する言語拡張たち
 型の表現力を高めたり、計算速度を向上させる種々の言語拡張について
5. FFI の話
 Haskell での FFI の扱いと、身近なライブラリから見る実装例について(※一部抜粋)
6. おまけ(その他便利グッズの話)
 あるとコーディングがある程度便利になる言語拡張について

一部訂正あり: http://qiita.com/func-hs/items/51b314a2323b83653bb9

松舘 剛志

February 14, 2016
Tweet

Other Decks in Programming

Transcript

  1. 目次 3頁 … はじめに 9頁 … GHC 7.8 からの変更点 69頁

    … Haskell が遅いと言われるワケとか 106頁 … 知らないと損する言語拡張たち 150頁 … FFI の話 161頁 … おまけ
  2. GHC 7.8 からの変更点 10頁 … Monad Proposal 22頁 … Foldable

    43頁 … Traversable 57頁 … Prelude の関数の変化 63頁 … 変更後の標準の型クラス全体図 67頁 … OpenGL から見る実装例
  3. Monad Proposal 正確には Functor-Applicative-Monad Proposal Monad のリファクタリングの提案 Monad は正式に Applicative

    の子型クラスに 話題自体は2014年よりも前から始まっていた。 最初期の Haskell では繋がっていたらしい。しかし… 親型クラスのメソッドをデフォルトで実装する機能がなかった。
  4. Monad Proposal instance 宣言のおさらい 記法 * instance C1 T1 where

    ... * instance C1 a => T1 a where ... * instance C1 a => Cn (T1 a) where ... 型クラスの実装をデータ型に対して与える
  5. Monad Proposal 型変数のあるデータ型への実装 instance C1 a => T1 a where

    f x = ... instance (C1 a, C2 a, ...) => T1 a where f x = ... インスタンスを与える型変数を明示する
  6. Monad Proposal 特定のインスタンスを持つデータ型への実装 instance C1 a => Cn (T1 a)

    where f x = ... instance (C1 a, C2 a,...) => Cn (T1 a) where f x = ... 特定する型クラスも明示する
  7. Monad Proposal 自動で実装できる型クラス ➜ deriving 句で導出できる型クラス Eq, Ord, Enum, Bounded,

    Read, Show コンストラクタの並び等から自明に導ける。 (もちろん自前で書いてもよい)
  8. Monad Proposal Monad と Applicative の実装は? ❌自明では導くことができない これら 2 つはコンテナの中身をどう扱うかの話

    ❌Applicative は Monad の親クラス Monad のインスタンスを持つ型が直接依存しているわけではない。
  9. Monad Proposal 公式の対応 return = pure Monad の return の実装を

    Applicative の pure にリファクタリング <*> = ap コンテナから値を取り出し関数を適用するという点で同義
  10. 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 コンテキストが変わるだけ
  11. 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
  12. Foldable 期待される性質 newtype Endo a = Endo {appEndo :: a

    -> a} モノイドのコンテキストで関数を合成する
  13. Foldable 期待される性質 newtype Endo a = Endo {appEndo :: a

    -> a} ➜ instance Monoid (Endo a) where mempty = Endo id Endo f `mappend` Endo g = Endo (f . g)
  14. Foldable 期待される性質 newtype Dual a = Dual {getDual :: a}

    値を合成させるためのコンテナ
  15. 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)
  16. Foldable Monoid に期待される性質 ✔ mappend mempty x = x ✔

    mappend x mempty = x ✔ mappend x (mappend y z) = mappend (mappend x y) z ✔ mconcat = foldr mappend mempty
  17. Foldable Monoid に期待される性質 mappend mempty x = x mappend x

    mempty = x 左単位元と右単位元の保証
  18. Foldable 話を戻して Foldable の話 foldr f z t = appEndo

    (foldMap (Endo . f) t) z foldl f z t = appEndo (getDual (Dual . Endo . flip f) t)) z 要素の畳み方を定義する必要がある
  19. Foldable 話を戻して Foldable の話 foldMap :: Monoid m => (a

    -> m) -> t a -> m foldMap f = foldr (mappend . f) mempty Foldable のインスタンスを持つコンテナの要素を Monoid に型変換させる
  20. Foldable 話を戻して Foldable の話 foldr :: (a -> b ->

    b) -> b -> t a -> b foldr f z t = appEndo (foldMap (Endo #. f) t) z コンテナの中身をモノイドのコンテキストに 乗せながら畳んでいく
  21. Foldable 話を戻して Foldable の話 foldr :: (a -> b ->

    b) -> b -> t a -> b foldr f z t = appEndo (foldMap (Endo #. f) t) z (´・_・`).oO((#.) ってなんだ?)
  22. Foldable 話を戻して Foldable の話 (#.) :: (b -> c) ->

    (a -> b) -> (a -> c) (#.) _f = coerce coerce :: Coercible * a b => a -> b coerce = let x = x in x 安全かつ強制的に型変換させるための関数
  23. Foldable Foldable まとめ ✔ 要素が Monoid のインスタンスであれば どんな値も畳むことができる ✔ Endo

    は関数合成用のコンテナ Dual は値合成用のコンテナ ✔ Monoid 用のデータ型は他にもあるよ!
  24. Traversable 最低限実装が必要なメソッド traverse :: Applicative f => (a -> f

    b) -> t a -> f (t b) traverse f = sequenceA . fmap f コンテナの要素を走査しながら関数を適用 していく
  25. Traversable 最低限実装が必要なメソッド traverse :: Applicative f => (a -> f

    b) -> t a -> f (t b) traverse f = sequenceA . fmap f コンテナが入れ替わることに注意
  26. Traversable 最低限実装が必要なメソッド sequenceA :: Applicative f => t (f a)

    -> f (t a) sequenceA = traverse id 走査するだけ (中の値自体には変更を加えない)
  27. Traversable 最低限実装が必要なメソッド sequenceA :: Applicative f => t (f a)

    -> f (t a) sequenceA = traverse id コンテナが入れ替わることに注意 (大事なことなのでry)
  28. Traversable 期待される性質 ✔ t . traverse f = traverse (t

    . f) ✔ traverse Identity = Identity ✔ traverse (Compose . fmap g . f) = Compose . fmap (traverse g) . traverse f
  29. Traversable 期待される性質 ✔ t . sequenceA = sequenceA . fmap

    t ✔ sequenceA . fmap Identity = Identity ✔ sequenceA . fmap Compose = Compose . fmap sequenceA . sequenceA
  30. Traversable 期待される性質 ✔ t . traverse f = traverse (t

    . f) ✔ t . sequenceA = sequenceA . fmap t 関数合成の自然性の保証 (関数合成の効率化)
  31. Traversable 期待される性質 ✔ traverse Identity = Identity ✔ sequenceA .

    fmap Identity = Identity 同一性の保証 (id がちゃんとそのままの値を返すこと)
  32. Traversable 期待される性質 ✔ traverse (Compose . fmap g . f)

    = Compose . fmap (traverse g) . traverse f ✔ sequenceA . fmap Compose = Compose . fmap sequenceA . sequenceA 結合則の保証
  33. 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) テクスチャ座標の畳込み等(一部抜粋)
  34. OpenGL から見る実装例 Foldable と Traversable はどう実装されているか instance Traversable TexCoord4 where

    traverse f (TexCoord4 x y z w) = pure TexCoord4 <*> f x <*> f y <*> f z <*> f w 座標変換等(一部抜粋)
  35. Haskell が遅いと言われるワケとか 70頁 … 評価しないものは溜まる 79頁 … データコンストラクタの中身 81頁 …

    文字列の計算量とメモリ使用量 85頁 … タプル 88頁 … 自作データ型の高速化 97頁 … 型クラスの仕組み 102頁 … 競技プログラミングでの Haskell
  36. 評価しないものは溜まる 評価予定の値もスタックに よくある階乗の計算 fact 0 = 1 fact n =

    n * fact n 実は未評価なまま次の計算に渡されている (評価されるのはパターンマッチの瞬間である)
  37. 評価しないものは溜まる 評価予定の値もスタックに よくある階乗の計算 fact 0 = 1 fact n =

    n * fact n 引数部に名前だけの場合、それはパターンマッチ されないことに注意。
  38. 評価しないものは溜まる その場で評価させる方法 階乗の例に適用してみる fact 0 = 1 fact n =

    n `seq` n * fact (n - 1) これにより、 n は seq 関数に渡され、その場で評 価されるようになった
  39. 文字列の計算量とメモリ使用量 計算量多い(確信) "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 回以上の計算が…
  40. 文字列の計算量とメモリ使用量 メモリ食いすぎィ! 構築子: 1 word 型引数: 1 word / arg

    文字列 1 文字分 = 5 words 構築子(:) + ポインタ * 2 + Char のサイズ 1 word 2 words 2 words C# + Char# ※32bit: 20 bytes, 64bit: 40 bytes
  41. タプル タプルも例外ではない (,) (Int, Int) P P I# Int# I#

    Int# (Int, Int, Int) (,) P P I# Int# P I# Int# I# Int#
  42. 自作データ型の高速化 標準にあるハッシュ付きの型は Unpack 可能 Int, Integer, Float, Double, Char, Word[N],

    Int[N] これらはライブラリではただの Unpack 可能な型 ➜ プリミティブな値は処理系が保持
  43. 自作データ型の高速化 標準にあるハッシュ付きの型は Unpack 可能 data T a = T {-#

    Unpack #-} !Int T Int# プリミティブな値がそのまま型引数に収まる ✔ 計算量とメモリ消費量が減る ✔ 生の値なので遅延評価不可 (正格性フラグ必須) ただのバイナリ
  44. 自作データ型の高速化 標準にあるハッシュ付きの型は Unpack 可能 data T a = T {-#

    Unpack #-} !Int T Int# プリミティブな値がそのまま型引数に収まる ❌ 構築子が 1 つだけの型に限る ❌ 通常の型と同じようには使えない ただのバイナリ
  45. 自作データ型の高速化 標準にあるハッシュ付きの型は Unpack 可能 data T a = T {-#

    Unpack #-} !Int T Int# プリミティブな値がそのまま型引数に収まる ❌ 複数あると処理系が判断できない ❌ 通常の型はポインタであると期待される ただのバイナリ
  46. 自作データ型の高速化 標準にあるハッシュ付きの型は Unpack 可能 data T a = T {-#

    Unpack #-} !Int T Int# プリミティブな値がそのまま型引数に収まる ❌ 複数あると処理系が判断できない ❌ 許してしまうと GC の対象になり、 厄介なバグを生みやすくなる ただのバイナリ
  47. 自作データ型の高速化 標準にあるハッシュ付きの型は Unpack 可能 data T a = T {-#

    Unpack #-} !Int T Int# プリミティブな値がそのまま型引数に収まる ❌ 複数あると処理系が判断できない ⚠ 不本意な再ボックス化を防ぐために 最適化オプションを付けるべき ただのバイナリ
  48. 型クラスの仕組み その正体は辞書 :: C a => a 1. C a

    という制約が導入されて、 2. C a の情報を持つ辞書に型 a の値が適用される
  49. 型クラスの仕組み その正体は辞書 :: C a => Hoge ❌ ➜ C

    a だけではインスタンスを特定できない
  50. 競技プログラミングでの Haskell 速度にシビアな問題では死ぬ ✔ 主に TLE で。 ➜ 標準の文字列や List

    等では間に合わない 問題がある ➜ それを埋め合わせるライブラリが使えない ➜ そもそも型の構造的に辛い…
  51. 競技プログラミングでの Haskell Haskell が使える競プロサービス AtCoder ✔ GHC 7.8 以降 Codeforces

    ✔ GHC 7.8 以降 他 ❌ (GHC がインストールされて)ないです…
  52. リテラルを置き換える Overloaded Lists Vector の例 instance Ext.IsList (Vector a) where

    type Item (Vector a) = a fromList = fromList fromListN = fromListN toList = toList
  53. リテラルを置き換える Overloaded Lists Vector の例 type Item (Vector a) =

    a Item (Vector a) という型の組み合わせを型 a として扱わせる言語拡張(ここでは触れない) See: 7.7 Type families - The User's Guide
  54. リテラルを置き換える Overloaded Lists Vector の例 fromList = fromListN List を

    Vector.fromListN に丸投げ (List の最初の N 要素だけを Vector に入れる)
  55. 計算量を削減する Unboxed Tuples Tuple からタグ(ポインタ部分)を取り除く ❌ (多層的な)型や関数の引数には渡せない ➜ 型ではない。 ➜

    ポインタを持っていない ➜ 引数ではない部分でなら問題ない (返り値にする時や case 式など)
  56. 計算量を削減する Unboxed Tuples (Int, Int) (,) P P I# Int#

    I# Int# (# Int, Int #) I# Int# I# Int# 連続した領域として 扱われる
  57. 型クラスの表現力を向上する Flexible Instances 通常時のルール ✔ 1 つの型クラスにつき型変数は 1 つまで ✔

    型変数の重複は許されない ✔ 制約は型変数にのみ与えられる ✔ 実装は必ずしなければならない
  58. 型クラスの表現力を向上する Flexible Instances 拡張導入後のルール ✔ instance C a b where

    ... ✔ instance C a a where ... ✔ instance C a Int where ... ✔ instance C a b (=> ...)
  59. 型クラスの表現力を向上する Flexible Instances 例1 class C a b instance C

    a Int instance C Int b a と b のどちらに Int があっても特定できてしまう
  60. 型クラスの表現力を向上する Flexible Instances 例2 class C a b instance C

    Int b instance C Int [b] 多相型なので一番目の b にも List があり得る
  61. 型クラスの表現力を向上する 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
  62. 型クラスの表現力を向上する Overlapping Instances よって、 :: C Int Int -> Int

    -> Int -> a という制約に対して instance C Int Int の定義がマッチされる
  63. 型クラスの表現力を向上する Incoherent Instances :: C Int Int -> Int ->

    Int -> a という制約に最も適合するインスタンスは instance C Int Int だが、これがない場合は
  64. FFIの話 151頁 … 呼び出せるプログラミング言語 154頁 … 呼び出し方 156頁 … Haskell

    での型の扱い 158頁 … ByteString から見る実装例 160頁 … hmatrix から見る実装例
  65. 呼び出せるプログラミング言語 対応状況 C ✔ 標準で使える C++ ▲ extern C 必須

    ❌ class や template は使用不可 (C と共通する部分のみ使用可)
  66. 呼び出し方 構文(import) foreign import callconv impent var :: ftype callconv

    := ccall | stdcall | cplusplus | jvm | dotnet impent := [string] (呼び出すヘッダの名前と関数の名前。スペース区切り。なくてもよい)
  67. 呼び出し方 構文(export) foreign export callconv expent var :: ftype callconv

    := ccall | stdcall | cplusplus | jvm | dotnet expent := [string] (呼び出すヘッダの名前と関数の名前。スペース区切り。なくてもよい)
  68. Haskell での型の扱い 型対応表 Haskell → C C → Haskell CInt

    HsInt CFloat HsFloat CDouble HsDouble Bool HsBool
  69. Haskell での型の扱い 型対応表 Haskell → C C → Haskell Ptr

    a * p (ポインタ) FunPtr a int (*p)(int) (関数ポインタ)
  70. ByteString から見る実装例 C の型を操作するので unsafe memchr :: Ptr Word8 ->

    Word8 -> CSize -> IO (Ptr Word8) memchr p w s = c_memchr p (fromIntegral w) s
  71. hmatrix から見る実装例 行列演算ライブラリ foreign import ccall unsafe "sort_indexD" c_sort_indexD ::

    CV Double (CV CInt (IO CInt)) C と Haskell の間を往復するのでオーバーヘッドが すごいらしい… (まだ使ったことがないので詳しくはなんとも…)
  72. あると便利な言語拡張たち ParallelListComp お馴染みフィボナッチ数列 fibs = 0 : 1 : [a

    + b | a <- fibs | b <- tail fibs] これは fibs = 0 : 1 : zipWith (+) fibs (tail fibs) と同じ