PureScriptで型制約を試す

# はじめに

簡単な具体例を「準備→実験→解説」の順で学びます。

## 準備

### 型クラスを作る

2つの型を取る型クラスを用意します。

```haskell:
class MyClass a b
```

### MyClassのインスタンスを作る

- 「Int型とString型を取るMyClass」
- 「String型とBoolean型を取るMyClass」
- 「Boolean型と任意の型を取るMyClass」

の3インスタンスを用意します。

```haskell:
instance myClassIntString     :: MyClass Int     String

instance myClassStringBoolean :: MyClass String  Boolean

instance myClassBooleanA      :: MyClass Boolean a
```

### 型制約のある関数を作る

- test1: 「MyClassの第1型引数にInt型を取るインスタンスの第2型引数の型」の値を取ってEffect Unit型の値を返す関数
- test2: 「MyClassの第2型引数にBoolean型を取るインスタンスの第1引数の型」の値を取ってEffect Unit型の値を返す関数
- test3: 「MyClassの第1型引数にBoolean型を取るインスタンスの第2引数の型」の値を取ってEffect Unit型の値を返す関数

の3関数を用意します。

```haskell:
test1 :: forall a. MyClass Int     a       => a -> Effect Unit
test1 _ = pure unit

test2 :: forall a. MyClass a       Boolean => a -> Effect Unit
test2 _ = pure unit

test3 :: forall a. MyClass Boolean a       => a -> Effect Unit
test3 _ = pure unit
```

ここで、それぞれの関数の第1引数の型は、関数の定義とMyClass型クラスのインスタンスの実装を照らし合わせ、当てはまるものが存在するかどうかを検査されます。

例えば、以下のように書くと、test0関数は第1引数に任意の型の値を受け入れるようになってしまうので、

```haskell:
test0 :: forall a. a -> Effect Unit
test0 _ = pure unit
```

このようにしても問題なく型検査を通ってしまいます。

```haskell:
test0 { afjeoa: [ "bbbbb", "fjeao" ], abc: { asaaa: { ldkfah: 3333, ioaejo: false } } }
```

```haskell:
-- こんな型、定義してない！
{ abc :: { asaaa :: { ioaejo :: Boolean                                                            
                    , ldkfah :: Int                                                                
                    }                                                                              
         }                                                                                         
, afjeoa :: Array String                                                                           
}     
```

## 実験

型と関数の定義を再掲しておきます。

```haskell:再掲
class MyClass a b

instance myClassIntString     :: MyClass Int     String
instance myClassStringBoolean :: MyClass String  Boolean
instance myClassBooleanA      :: MyClass Boolean a

test1 :: forall a. MyClass Int     a       => a -> Effect Unit
test1 _ = pure unit

test2 :: forall a. MyClass a       Boolean => a -> Effect Unit
test2 _ = pure unit

test3 :: forall a. MyClass Boolean a       => a -> Effect Unit
test3 _ = pure unit
```

### 実験1

test1関数を使ってみます。

```haskell:
test1 "hello"  -- Success
test1 3        -- Error!
test1 false    -- Error!
```

String型以外の値を渡した場合はコンパイルエラーになりました。

### 解説(実験1)

test1関数の定義では、MyClass型クラスの第1型引数の型がInt型であるインスタンスによって制約がかかっています。インスタンス宣言を見ると、インスタンスmyClassIntStringがこれに当てはまり、test1関数の定義にある型変数aはStringであることがわかります。したがって、この関数にString型以外の値を与えた場合はコンパイルエラーになります。

### 実験2

test2関数を使ってみます。

```haskell:
test2 "hello"  -- Success
test2 3        -- Error!
test2 false    -- Error!
```

これも（同じ結果になったのはたまたまですが）、String型以外の値を渡した場合はコンパイルエラーになりました。

### 解説(実験2)

test2関数の定義では、MyClass型クラスの第2型引数の型がBoolean型であるインスタンスによって制約がかかっています。インスタンス宣言を見ると、インスタンスmyStringBooleanがこれに当てはまり、test2関数の定義にある型変数aはStringであることがわかります。したがって、この関数にString型以外の値を与えた場合はコンパイルエラーになります。

### 実験3

test3関数を使ってみます。

```haskell:
test3 "hello"  -- Success
test3 3        -- Success
test3 false    -- Success
```

全て型検査を通りました。

### 解説(実験3)

test3関数の定義では、MyClass型クラスの第1型引数の型がInt型であるインスタンスによって制約がかかるはずでしたが、インスタンス宣言を見ると、対応するインスタンスはmyClassBooleanAであることがわかります。そして、test3関数の定義にある型変数aは型の指定がなく、任意の型を受け入れてしまうことがわかります。


## 本記事で使用したソースコード

```haskell:Main.purs
module Main where

import Prelude

import Effect (Effect)

class MyClass a b

instance myClassIntString     :: MyClass Int     String
instance myClassStringBoolean :: MyClass String  Boolean
instance myClassBooleanA      :: MyClass Boolean a

main :: Effect Unit
main = do
   test1 "hello"  -- Success
   --test1 3      -- Error!
   --test1 false  -- Error!

   test2 "hello"  -- Success
   --test2 3      -- Error!
   --test2 false  -- Error!

   test3 "hello"  -- Success
   test3 3        -- Success
   test3 false    -- Success

test1 :: forall a. MyClass Int     a       => a -> Effect Unit
test1 _ = pure unit

test2 :: forall a. MyClass a       Boolean => a -> Effect Unit
test2 _ = pure unit

test3 :: forall a. MyClass Boolean a       => a -> Effect Unit
test3 _ = pure unit
```

# まとめ

代数的データ型や高階関数、高階多相型などを用いればさらに複雑な定義も可能です。

まだまだ勉強することが多いですが、制約を使いこなして、便利でわかりやすく型安全なコードを書きましょう！