Eq y => (Integer -> Bool) -> y
Eq
のインスタンスになる、すなわち等号(==)
で等しいかどうか判定できることを実装を通して見ていきたいと思います。Integer -> Bool
という関数を扱うための便利な演算子を用意しておきます。(#) :: Bool -> (Integer -> Bool) -> (Integer -> Bool)
x # a = \i -> if i == 0 then x else a (i-1)
-- ~~~~~~~~~~ Example ~~~~~~~~~~ --
ex1 :: Integer -> Bool
ex1 = True # const False
ex1 0
ex1 1
ex1 2
(#)
は Integer -> Bool
を添え字を取って真偽値を返す無限リストだと思った時、先頭に値を追加する演算子と見做すことができます。コードを実行すると期待通りの挙動になっていることがわかるでしょう。Integer
ですがこの記事を通して正の範囲の値しか与えられないこととします。つまり実質 Natural
として扱います。ただ全ての関数を適切に修正すれば負の範囲も考慮した実装も可能でしょう。find :: ((Integer -> Bool) -> Bool) -> (Integer -> Bool)
find p =
if forsome (\a -> p (False # a))
then False # find (\a -> p (False # a))
else True # find (\a -> p (True # a))
forsome :: ((Integer -> Bool) -> Bool) -> Bool
forsome p = p (find p)
forevery :: ((Integer -> Bool) -> Bool) -> Bool
forevery p = not $ forsome (not . p)
-- ~~~~~~~~~~ Example ~~~~~~~~~~ --
ex2 :: Integer -> Bool
ex2 = find (\a -> a 1 && a 3)
ex2 0
ex2 1
ex2 2
ex2 3
ex2 4
find
は与えられた条件を満たす Integer -> Bool
を1つ返す関数です。この関数は内部でforsome
を呼んでおり、forsome
も内部でfind
を呼んでいるので相互再帰になっています。一見複雑ですが、挙動としてはInteger -> Bool
の値を前から1つずつ仮置きで定めていき条件に合致するものが見つかったらそれを返すという挙動になっています。そのため述語である (Integer -> Bool) -> Bool
という関数は有限時間で決定できるものでなくてはなりません。find
の挙動のもう一つの特徴として以下の例を見てみましょう。ex3 :: Integer -> Bool
ex3 = find (const False)
ex3 0
ex3 1
ex3 2
find
に与える述語は常にFalse
を返すようになっているため条件に合致する Integer -> Bool
は存在しませんが、それでも find
は何らかの値を返します。forsome
関数は述語に合致する Integer -> Bool
が存在するかどうかを判定する関数ですが、このような find
の挙動のため find
が値を返したとしてももう一度それが述語に適合するものかどうかを調べているのです。forevery
は全ての Integer -> Bool
が与えられた述語を満たすかどうかを調べる関数で、これはドモルガンの法則を用いて forsome
を利用して実装されています。Eq
インスタンスを定義してみましょう。{-# LANGUAGE FlexibleInstances #-}
instance Eq y => Eq ((Integer -> Bool) -> y) where
f == g = forevery (\a -> f a == g a)
-- ~~~~~~~~~~ Example ~~~~~~~~~~ --
ex4 :: (Integer -> Bool) -> Bool
ex4 a = a 1 && a 2
ex5 :: (Integer -> Bool) -> Bool
ex5 a = not (not (a 1) || not (a 2))
ex4 == ex5