Help us understand the problem. What is going on with this article?

PureScriptで型制約を試す

More than 1 year has passed since last update.

はじめに

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

準備

型クラスを作る

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

class MyClass a b

MyClassのインスタンスを作る

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

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

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関数を用意します。

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引数に任意の型の値を受け入れるようになってしまうので、

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

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

test0 { afjeoa: [ "bbbbb", "fjeao" ], abc: { asaaa: { ldkfah: 3333, ioaejo: false } } }
-- こんな型、定義してない!
{ abc :: { asaaa :: { ioaejo :: Boolean                                                            
                    , ldkfah :: Int                                                                
                    }                                                                              
         }                                                                                         
, afjeoa :: Array String                                                                           
}     

実験

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

再掲
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関数を使ってみます。

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

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

解説(実験1)

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

実験2

test2関数を使ってみます。

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

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

解説(実験2)

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

実験3

test3関数を使ってみます。

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

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

解説(実験3)

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

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

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

まとめ

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

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

matoruru
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away