purescript

はじめに

遅くれてしまいましたが、今回は小ネタです。 purescript-nonempty ライブラリを共有したいと思います。

前回は Parital制約 について書きました。Partial制約はあくまで注意を促すだけであり、ユーザが気を付けて使う必要があります。Parital関数を使うのは邪道でありよほどのことがない限りTotal関数を定義すべきです。前回の例では配列の先頭要素を取り出す head 関数を例に挙げました。この関数をTotal関数とする一つの方法には結果の型を Maybe で包むというのがありました。もう一つの取り上げなかった方法として引数の型を適切な型に変えるというものがあります。単なる「要素aの配列」ではなく、「空ではない要素aの配列」という型があれば、head 関数はMaybeで包まなくても必ず値を返すことができます。head に限らず、正確なモデリングを行なうには「空ではない要素aの配列」という型が欲しくなるケース多いと思います。

この「空ではない要素aの配列」を自前で定義してもいいのですが、既にpurescript-nonempty ライブラリがこの型を表現する方法を提供しています。実際には配列に限らず、より汎用的に「空ではない要素aのコンテナ型」が表現できるようになっています。

NonEmpty

purescript-nonempty ライブラリの Data.NonEmpty に以下の NonEmpty 型が定義されています。fがベースとなるコンテナ型、aが要素の型です。

data NonEmpty f a = NonEmpty a (f a)

例えば空でないInt配列の型は NonEmpty Array Int になります。NonEmpty コンスラクタのシノニムとして (:|) 演算子が定義されています。

foo :: NonEmpty Array Int
foo = 4 :| []               -- [4]

bar :: NonEmpty Array Int
bar = 1 :| [2,3]            -- [1,2,3]

NonEmpty f a に対して通常コンテナ型に求められる Functor, Foldable,Traversable 型クラスのインスタンスが定義されています。そのため他のコンテナ型と扱い方は基本的に同じになります。

「空ではない」という性質から独自の関数を提供しています。Maybe で包まなくても head, tail 関数はTotal関数です。

head :: forall f a. NonEmpty f a -> a
tail :: forall f a. NonEmpty f a -> f a

また必ず空でないことが保証されているので foldl 関数の初期値指定なしバージョンの foldl1 関数が使えます。

foldl1 :: forall f a. Foldable f => (a -> a -> a) -> NonEmpty f a -> a

便利ですね!

まとめ

短くなりましたが、 purescript-nonempty ライブラリの共有でした。
横着して、空でないことが分かっているところでも通常のコンテナ型とか使っていましたが、NonEmpty を使えば簡単により正確な型が得られるのでこの先必要あれば使っていきたいと思っています。