Edited at

Haskellの型クラスでJavaScriptっぽいことをやって遊んでみる

すごいH本(すごいHaskellたのしく学ぼう!)で弱く型付けされた言語の例でYesNo型クラスを作って遊んでいるのを面白そうだと思ったので少し試してみます。


準備


  • 空っぽのHaskellのソースファイル

空っぽのHaskellのソースファイルWeakTyping.hsを用意してください。


WeakTyping.hs

module WeakTyping.hs where

-- ここにソースを書いていきますー


  • GHCのREPL環境

各自の環境に合わせてWeakTyping.hsをインポートしてghciでREPLを起動できるようにしてください。

うまくいくと次のようなコンソールが表示されると思います。

WeakTyping>

WeakTyping.hsを何度も書き換えます。書き換えたら適宜:reloadなどで更新してください。


真らしい値と偽らしい値

真らしい値(truthy)と偽らしい(falsy)を判定してみましょう。例えば、JavaScriptだと真偽値でないものをifで評価できます。

> if (1) { "yes" } else { "no" }

'yes'


Boolable型クラス

真らしさを判定できるBoolable(bool + ableの造語です)型クラスを作ってみましょう。

truthyは引数のデータが真らしいならTrueを返します。falsytruthyの否定ですね。


WeakTyping.hs

class Boolable a where

truthy :: a -> Bool
falsy :: a -> Bool
if' :: a -> b -> b -> b
truthy = not . falsy -- デフォルト実装
falsy = not . truthy -- デフォルト実装
if' cond t f
| truthy cond = t
| falsy cond = f

デフォルト実装をtruthyfalsyif'の3つに定義しているのは型クラスの完全最小主義(minimal complate definition)に従っているからです。最小限の定義でインスタンスにするための考え方です。インスタンスはtruthyfalsyのどちらかのメソッドを定義するだけで済みます。


Integerのインスタンス

では、IntegerBoolableのインスタンスにしてみます。


WeakTyping.hs

instance Boolable Integer where

truthy 0 = False
truthy _ = True

ghciWeakTyping.hsを試してみます。

> truthy 1

True
> truthy 0
False
> if' 1 "yes" "no"
"yes"


Maybe aのインスタンス

Maybe aJustが真っぽく、Nothingが偽っぽいのでそのようにインスタンスを作ってみましょう。


WeakTyping.hs

instance Boolable (Maybe a) where

truthy (Just _) = True
truthy Nothing = False

> truthy (Just 1)

True
> truthy Nothing
False


[a]のインスタンス

空リストは偽っぽいのでそのようにインスタンスを作ってみます。


WeakTyping.hs

instance Boolable [a] where

truthy [] = False
truthy _ = True


Boolのインスタンス

Boolはもちろんそのままですね。そのままなので恒等関数'id'を使って実装します。


WeakTyping.hs

instance Boolable Bool where

truthy = id


曖昧な比較

JavaScriptでは==は曖昧な比較で型が違う場合でも適当にキャストされて真偽値を返してくれます。似たような比較を実装してみたいと思います。


Similar型クラス

単に値が似ているという意味でSimilar型クラスを作ってみます。


WeakTyping.hs

class Similar a b where

(~=) :: a -> b -> Bool

実はこのままだとコンパイルが通りません。

error:

• Too many parameters for class ‘Similar’
(Enable MultiParamTypeClasses to allow multi-parameter classes)
• In the class declaration for ‘Similar’

パラメータが多すぎると言われていますね。さらにMultiParamTypeClassesを有効にしろと書いてあります。MultiParamTypeClassesってなんでしょうか?ググるとGHCの言語拡張ですね。

型引数が2つ以上の型クラスを作るには拡張が必要のようなので、ソースコードの冒頭に拡張を追加しておきましょう。


WeakTyping.hs

{-# LANGUAGE MultiParamTypeClasses #-}


これで無事にコンパイルが通ります。


IntegerStringのインスタンス

次にIntegerStringを比較できるようにSimilarのインスタンスにしてみましょう。今回は数値側を文字列に変換して比較する実装とします。


WeakTyping.hs

instance Similar Integer String where

a ~= b = (show a) == b

また、コンパイルエラーとなりました。

error:

• Illegal instance declaration for ‘Similar Integer String’
(All instance types must be of the form (T t1 ... tn)
where T is not a synonym.
Use TypeSynonymInstances if you want to disable this.)
• In the instance declaration for ‘Similar Integer String’

型シノニムをインスタンスにできないようですね。String[Char]の型シノニムなのでそれがいけないようです。先ほどと同じように言語拡張を使って解決できるようなので、TypeSynonymInstancesを冒頭に追加してみます。


WeakTyping.hs

{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances #-}


これでコンパイルが通ると思いきやさらに新しいエラーとなります。

error:

• Illegal instance declaration for ‘Similar Integer String’
(All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
Use FlexibleInstances if you want to disable this.)
• In the instance declaration for ‘Similar Integer String’



(T a1 ... an)の形で型引数に渡す必要があるようです。具体的にはMaybe StringEither Integer Stringのようなことでしょう。少なくとも今のInteger Stringはこれに当てはまらない形なのでエラーとなるようです。

String[Char]のシノニムですが、拡張なしでこのようなネストされた具体的な型を含む指定はできないようです。[a]はOKで[Char][Integer]はNGです。(@udongohanより指摘をいただきました)

これを無効にするにはFlexibleInstancesを使えば良いと書いてあるので素直にしたがってみます。

冒頭を次のように書き換えて再度コンパイルしてみます。


WeakTyping.hs

{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-}


これで通りましたね!

ちなみにFlexibleInstancesTypeSynonymInstancesを含んでいるので、TypeSynonymInstancesの拡張は外しても構いません。

ghciで試してみます。

> 1 ~= "2"

False
> 1 ~= "1"
True

逆に"2" ~= 1のように逆に比較する場合は次のインスタンスを定義します。(冗長な気がするので簡単に書く方法があれば知りたい。。。)


WeakTyping.hs

instance Similar String Integer where

a ~= b = a == show b


同じ型a同士のインスタンス

さらに既存の==で比較できるデータも~=で比較できるようにしてみます。これは次のようにEq型クラスに属する同じ型a==をそのまま使えばいいですね。


WeakTyping.hs

instance Eq a => Similar a a where

(~=) = (==)

> 1 ~= 2

False
> 1 ~= 1
True


まとめ

Haskellも型クラスをうまく使うと柔軟で面白いことができます。もちろん実際にはこんな使い方をすることは稀だと思いますが。こんな風に遊んでみるのも勉強になりますね。