-
Notifications
You must be signed in to change notification settings - Fork 2
yaakaito のための Haskell ライブラリの読み方
ソースコードがなんとなく読めても、ライブラリ、モジュールの読み方は要領が違うので書く
コードの読み方 も合わせて読もう
##「これ、何…???」 hackage 行って調べる ##さっと読みたい
- hackageDB からパッケージのページ行く
- パッケージの Properties 見る
- 各モジュールのページを括りの大きい方から見ていく
- 一番大きい括りのモジュールには大体ライブラリの目的やサンプルコード書いてあったりするからそっち読む
- モジュールの公開API の型シグネチャ見てもわからなかったらソース読む
- ギッハブにも公開してあったらテストコードで具体的な使い方調べてみる
- 他のパッケージから依存してたら、釈然としてない場合そっちも見に行ってみる
- 「やっぱりわからなかったよ…」
##ちゃんと知りたい ###Cabal User's Guide パッケージマネージャツール。
ビルドツールであり、パッケージマネージャであり、パッケージインストーラである。 ###Haddock User's Guide ドキュメント生成ツール。
haddock での各モジュールのドキュメントはこれで生成されている。
###Programming guidelines 大体の開発者は守ってる(はず)
###「オゥ、ワタシ、エイゴ、ワカリマセン」 がんばって!!
##ライブラリの良し悪しのパッと見チェック ###パッケージ情報見て、生きてる(更新ちゃんとやってる、動作検証してる)ライブラリかどうか hackage のライブラリトップページ見て、package properties を見る。
ここで他に依存してるライブラリを辿ったり、処理系のバージョンとの相性などをチェックできる。
Versions <バージョン情報>
Dependencies <他に依存関係のあるライブラリパッケージ>
License <ライセンス情報>
Copyright <著作権情報>
Author <作者の連絡先>
Maintainer <メンテナーの連絡先>
Category <パッケージの機能的カテゴリ>
Home page <ほむっぺ>
Bug tracker <バグ報告先>
Source repository <リポジトリのパス等>
Upload date <アップロード日付>
Uploaded by <誰さんが更新したか>
Built on <どの処理系、バージョンでビルド確認したか>
Distributions <どのOSのパッケージマネージャで提供してるか>
###枯れて安定してるのか、実験的な試作なのか 各モジュールのページ行って、ヘッダに書いてある↓を見て判断する。
書いてないモジュールは、悪いライブラリだから信用しない方が良い ( Stability と Portability の項が無いのは特に )
Module : <モジュール名>
Description : <モジュールの一言説明>
Copyright : <これの著作権利者>
License : <ライセンス>
Maintainer : <メンテナーへの連絡先>
Stability : 不安定 ← [ unstable | experimental | provisional | stable | frozen ] → 安定
Portability : [ portable | non-portable (<理由>) ] どの処理系でも動くかどうかと、動かないならその理由や動く処理系の指定を書く
###ソースへのリンクが無いんだけど… 考えられる原因は2つ
-
ライブラリのバージョンが古い -> 新しいのが無いか調べる
-
最新バージョンでもリンクが無い‥
-
諦めよう (っていうか使わない)
-
頑張って .tgz 落としてエディタで読む
-> 苦行
-
###「テストしてあんのこれ…?」 テストコードは hackage 上で公開されてないことが多い。
幸い、最近のライブラリ作者は github にもソースを公開してることが多く、そっち行くとテストコードが読める。
テストコードの方がより具体的な使い方を把握しやすかったりする。
##ライブラリのソースの読み方
module Hoge ( -- module (カテゴリ).(パッケージ).(モジュール).(モジュールのコア実装) みたいなのが最近の流行っぽいような
hoge, -- 関数 hoge はエクスポートするよ宣言
Foo(..) -- Foo (型,データコンストラクタ または 型クラス) のアクセサやメソッドをまとめてエクスポートするよ宣言
) where
import Hoge.Huga.Foo (Foo(..)) -- Hoge.Huga.Foo モジュールから、Foo関係のものだけインポートするよ
import Hoge.Huga.Bar -- Hoge.Huga.Bar は全部インポートするよ
import qualified Data.Text as T -- Data.Text を全部インポートするけど、名前空間衝突を避ける為にモジュールのエイリアスを T として宣言するよ
-- import qualified でエイリアスをつけた関数はこう使われる
buzz = T.wrap "Buzz" -- ちなみに buzz はエクスポート対象じゃないので、 import Hoge しても呼び出す事は出来ない。
###追いかけ方 ####エクスポート宣言で公開されている関数や型、型クラスをおさえる 全部いきなり読もうとはせず、 haddock で 公開API として提供されているもののみざっと把握しよう。
親切なパッケージはどういう風に使って欲しいのかサンプルコードをドキュメントに書いてくれたりする。
パッケージツリーの先頭から先に読んで概要を把握するところからはじめる。
####型より型クラスの方を覚えておく ちゃんとしたライブラリは特定の型に対して関数を宣言するより、型クラスのインスタンス宣言で合理的にコントロールする方を好む。
型クラスでコントロールする方針はライブラリユーザにとっても拡張性が高い利点がある。
何で 「モナド モナド」 って 「メガネ メガネ」 みたいな感じで Haskeller が鳴き声出すのかは、そういうことだと思っておくと気がラク
####関数の実装より型シグネチャを先に見ておく よく出来たライブラリは関数の型シグネチャだけで何をやるかわかる。
length :: [a] -> Int -- 「ああ、リストから長さ取るからそりゃそうですね…」と思わざるを得ない例
逆に名前が抽象的すぎる上に型シグネチャが任意型ばかりなものはコンビネータ (関数型言語らしさ溢れるユーティリティ) だと見当つけておくと大体あってる
#####QuickCheck.Gen
のコンビネータの例
sized :: (Int -> Gen a) -> Gen a -- 乱数の範囲を整数で指定してジェネレータを作る関数から、サイズを内部で決めてジェネレータを作る
resize :: Int -> Gen a -> Gen a -- ジェネレータから生成された値を、別の乱数の範囲で生成しなおす
choose :: Random a => (a, a) -> Gen a -- ランダムに値を生成できる2つの値の組から、その範囲内のジェネレーターを作る
promote :: Monad m => m (Gen a) -> Gen (m a) -- 外部入力やリソースから読み込んだジェネレーターを、テストに利用できるようにする
suchThat :: Gen a -> (a -> Bool) -> Gen a -- ジェネレーターを「生成された値が特定の条件に合うように」加工する
suchThatMaybe :: Gen a -> (a -> Bool) -> Gen (Maybe a)
-- ジェネレーターを「生成された値が特定の条件に合うなら Just, 合わなければ Nothing に」加工する
oneof :: [Gen a] -> Gen a -- ジェネレーターのリストから、一つ選ばせる
frequency :: [(Int, Gen a)] -> Gen a -- [(比率, ジェネレーター)] のリストから、選択に偏りがあるようにジェネレーターを選ばせる
elements :: [a] -> Gen a -- リストの中から一個
growingElements :: [a] -> Gen a -- 無限リストからジェネレーターを作る
listOf :: Gen a -> Gen [a] -- ジェネレーターから、空リストを含む、リストのジェネレーターを作る
listOf1 :: Gen a -> Gen [a] -- 空じゃない(最少でも一個は要素のある)リストのジェネレーターを作る
vectorOf :: Int -> Gen a -> Gen [a] -- 固定長リスト
####案外 type
と newtype
で混乱しがち
実は単に String
のシノニム(別名)を振っていただけだったりとかある。
type Property = Gen Prop -- Prop のジェネレータでしかないよー
newtype Prop = MkProp{ unProp :: Rose Result } -- Prop は Result で出来た Rose (多分木) を特殊に使ってるだけだよー
data Rose a = MkRose a [Rose a] | IORose (IO (Rose a)) -- Rose はこんなデータ型だよー
data Result = MkResult -- Result はこんなデータ型だよー
{ ok :: Maybe Bool
, expect :: Bool
, reason :: String
-- ...
}
####「なんか黒魔術っぽい…」
ビット演算が出来る Bits
型クラスのインスタンス宣言とかに遭遇するとこういう C++のマクロみたいなの と混合したコードとか出てくる
instance Bits Int where
{-# INLINE shift #-}
#ifdef __GLASGOW_HASKELL__
(I# x#) .&. (I# y#) = I# (word2Int# (int2Word# x# `and#` int2Word# y#))
(I# x#) .|. (I# y#) = I# (word2Int# (int2Word# x# `or#` int2Word# y#))
(I# x#) `xor` (I# y#) = I# (word2Int# (int2Word# x# `xor#` int2Word# y#))
complement (I# x#) = I# (word2Int# (int2Word# x# `xor#` int2Word# (-1#)))
(I# x#) `shift` (I# i#)
| i# >=# 0# = I# (x# `iShiftL#` i#)
| otherwise = I# (x# `iShiftRA#` negateInt# i#)
(I# x#) `shiftL` (I# i#) = I# (x# `iShiftL#` i#)
(I# x#) `unsafeShiftL` (I# i#) = I# (x# `uncheckedIShiftL#` i#)
(I# x#) `shiftR` (I# i#) = I# (x# `iShiftRA#` i#)
(I# x#) `unsafeShiftR` (I# i#) = I# (x# `uncheckedIShiftRA#` i#)
{-# INLINE rotate #-} -- See Note [Constant folding for rotate]
(I# x#) `rotate` (I# i#) =
I# (word2Int# ((x'# `uncheckedShiftL#` i'#) `or#`
(x'# `uncheckedShiftRL#` (wsib -# i'#))))
where
!x'# = int2Word# x#
!i'# = word2Int# (int2Word# i# `and#` int2Word# (wsib -# 1#))
!wsib = WORD_SIZE_IN_BITS# {- work around preprocessor problem (??) -}
bitSize _ = WORD_SIZE_IN_BITS
popCount (I# x#) = I# (word2Int# (popCnt# (int2Word# x#)))
#else /* !__GLASGOW_HASKELL__ */
lv.1 で バラモス倒すみたいなプレイはやめよう ライブラリ利用するだけだったら、ここまで読み込む必要はそうそうない ####「ちゃんと説明してよ」 はい。
かいつまんで言うと 処理系ごとで違う実装 や、 コンパイル時に最適化するため などで低レイヤーで実装をやってる。
ソースコードファイルの先頭で {-# Language CPP #-}
とか、ソースの真ん中で {-# INLINE hoge #-}
とかが出てきたらプリコンパイル用のハックをしていると思えば良い。
##そのほかのポイント 思いついたら書く