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

175
───When You Will Try Haskell これから Haskell を書くにあたって

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

───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 Proposal22頁 … Foldable43頁 … Traversable57頁 … Prelude の関数の変化63頁 … 変更後の標準の型クラス全体図67頁 … OpenGL から見る実装例

Monad Proposal

正確には Functor-Applicative-Monad ProposalMonad のリファクタリングの提案

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 / appEndoDual / 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 = xmappend 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) zfoldl 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 -> mfoldMap f = foldr (mappend . f) mempty

Foldable のインスタンスを持つコンテナの要素をMonoid に型変換させる

Foldable

話を戻して Foldable の話

foldr :: (a -> b -> b) -> b -> t a -> bfoldr f z t = appEndo (foldMap (Endo #. f) t) z

コンテナの中身をモノイドのコンテキストに乗せながら畳んでいく

Foldable

話を戻して Foldable の話

foldr :: (a -> b -> b) -> b -> t a -> bfoldr f z t = appEndo (foldMap (Endo #. f) t) z

(´・_・`).oO((#.) ってなんだ?)

Foldable

話を戻して Foldable の話

(#.) :: (b -> c) -> (a -> b) -> (a -> c)(#.) _f = coercecoerce :: Coercible * a b => a -> bcoerce = 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 = 1fact n = n * fact (n - 1)

この n は何の問題もないように見えるが…

評価しないものは溜まる

評価予定の値もスタックに

よくある階乗の計算

fact 0 = 1fact n = n * fact n

実は未評価なまま次の計算に渡されている(評価されるのはパターンマッチの瞬間である)

評価しないものは溜まる

評価予定の値もスタックに

よくある階乗の計算

fact 0 = 1fact n = n * fact n

引数部に名前だけの場合、それはパターンマッチされないことに注意。

評価しないものは溜まる

評価予定の値もスタックに

呼び出し回数が少ない内は無事に動く➜ スタックもそれほど溜まらない

評価しないものは溜まる

評価予定の値もスタックに

しかし呼び出す回数が増えるにつれて、スタックはどんどん溜まっていき、いずれは溢れてしまう➜ 渡された値も未評価のまま…

この現象をスペースリークという

評価しないものは溜まる

評価予定の値もスタックに

対処法✔ 他の関数に渡す前に強制的に評価させる➜ seq 関数(この節で説明)➜ BangPatterns(148頁)

評価しないものは溜まる

その場で評価させる方法

seq 関数渡された値をその場で評価する(評価するだけ)

seq :: a -> b -> bseq = let x = x in x

評価しないものは溜まる

その場で評価させる方法

階乗の例に適用してみる

fact 0 = 1fact 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 Intinstance Eq TT where ...

型クラスの表現力を向上する

Flexible Instances

注意点⚠ 柔軟化した分、処理系は実装を 特定しにくくなる

型クラスの表現力を向上する

Flexible Instances

例1class C a binstance C a Intinstance C Int b

a と b のどちらに Int があっても特定できてしまう

型クラスの表現力を向上する

Flexible Instances

例2class C a binstance C Int binstance 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 Intinstance C Int binstance C Int Int

型クラスの表現力を向上する

Overlapping Instances

この内、最も特殊性が高いのは茶色の定義

instance C Int binstance C a Intinstance C Int Int

型クラスの表現力を向上する

Overlapping Instances

よって、:: C Int Int -> Int -> Int -> aという制約に対してinstance C Int Intの定義がマッチされる

型クラスの表現力を向上する

Overlapping Instances

⚠ 特殊性の高いインスタンスがひとつもないと 判断された場合はエラーになる

型クラスの表現力を向上する

Overlapping Instances

instance C Int binstance C a Int

だけだと特定のしようがない…(エラー)

型クラスの表現力を向上する

Incoherent Instances

特殊性のあるインスタンスが 1 つも見つからなかった場合に、最もパターンの近いインスタンスを選ばせる

型クラスの表現力を向上する

Incoherent Instances

:: C Int Int -> Int -> Int -> aという制約に最も適合するインスタンスはinstance C Int Intだが、これがない場合は

型クラスの表現力を向上する

Incoherent Instances

instance C Int binstance 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 :: ftypecallconv := ccall | stdcall | cplusplus | jvm | dotnetimpent := [string](呼び出すヘッダの名前と関数の名前。スペース区切り。なくてもよい)

呼び出し方

構文(export)

foreign export callconv expent var :: ftypecallconv := ccall | stdcall | cplusplus | jvm | dotnetexpent := [string](呼び出すヘッダの名前と関数の名前。スペース区切り。なくてもよい)

Haskell での型の扱い

型対応表

Haskell → C C → Haskell

CInt HsInt

CFloat HsFloat

CDouble HsDouble

Bool HsBool

Haskell での型の扱い

型対応表

Haskell → C C → HaskellPtr 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 -> Boolf x | n <= x && x <= m = ... | otherwise = ...

あると便利な言語拡張たち

PatternGuards

引数のパターンマッチの柔軟性が増す

複数の条件を同時に書けるf :: Int -> Boolf x | n <= x && x <= m, x >= n * 100 = ... | otherwise = ...

あると便利な言語拡張たち

PatternGuards

引数のパターンマッチの柔軟性が増す

これは(||)を適用してるのと同じf :: Int -> Boolf 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!