PartialFunction in Haskell

#scala にあって他の関数型言語には見られない機能として、PartialFuncttionがあるが、良いものだと思う(命名は良くない。CheckableFunctionとか他の名前が良かった気がする)。おかげでcollectみたいなメソッドが定義できるし、他にも応用が利く。

という @kmizu さんの発言があったので Haskell でもやっときます。

{-# LANGUAGE TypeOperators #-}

import Data.Maybe

type Open a = a -> a

fix :: (a -> a) -> a
fix f = f (fix f)

close :: Open a -> a
close = fix

type (a :->: b) = Open (a -> Maybe b)

undef :: a :->: b
undef f a = Nothing

applyMaybe :: a :->: b -> a -> Maybe b
applyMaybe pf = close (pf . undef)

defined :: a :->: b -> a -> Bool
defined pf = isJust . applyMaybe pf

apply :: a :->: b -> a -> b
apply pf = fromJust . applyMaybe pf

collect :: a :->: b -> [a] -> [b]
collect pf [] = []
collect pf (x:xs) = if defined pf x then (apply pf x) : collect pf xs else collect pf xs

zero :: Int :->: String
zero f 0 = Just "0"
zero f n = f n

one :: Int :->: String
one f 1 = Just "1"
one f n = f n

two :: Int :->: String
two f 2 = Just "2"
two f n = f n

こんな感じで…

*Main> collect undef [0..3]
[]
*Main> collect zero [0..3]
["0"]
*Main> collect (one . two) [0..3]
["1","2"]

こんな具合。Just と定義しない部分がダサすぎですね…!まあ Scala と違って文法サポートがないので仕方がない。

COMFRK vol3 でもチラリと書きましたが、Open Recursion は部分関数を実現するためにも使えます。ただ、定義されているかどうかみたいなのは素朴な定義では分からないので、定義されているかどうかの情報を戻り値に持たせるために Maybe に包んでやっています。Haskell なら laziness がうまくはまって defined では Just の中身は評価されないので、こんなにシンプルに実装できてハッピーです。OCaml だとどうかな…

真面目にやるなら、部分関数として扱えるものは a -> Maybe b だけではないので、部分関数のための型クラスを作ってやって、[(a, b)] とか Map a b とかもインスタンスにしとくべきでしょうね。Scala ではそうなっています

class PartialFunction to where
  applyMaybe :: a `to` b -> a -> Maybe b
  apply pf = fromJust . applyMaybe pf
  defined pf = isJust . applyMaybe pf

そんなわけで 2011 年最後の小ネタでした。もうほんと眠いです。意地で起きてる。