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

1,312 views

Published on

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

Published in: Technology
0 Comments
8 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,312
On SlideShare
0
From Embeds
0
Number of Embeds
82
Actions
Shares
0
Downloads
50
Comments
0
Likes
8
Embeds 0
No embeds

No notes for slide

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

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

×