概要
Haskell における関数の定義方法として、 ポイントフリースタイルというものがあります。 これは、 仮引数を使わずに関数適用と合成だけで関数を定義するスタイルのことで、 こちらの方がより簡潔に分かりやすいコードになることがあります。 このページには、 仮引数を使った関数定義をポイントフリースタイルに書き換えるときに使えそうなテクニックをまとめてあります。
なお、 ポイントフリースタイルにすることでかえって式が煩雑になることも多いです。 このページに記載したテクニックは、 ポイントフリースタイルでより見やすくなるであろうパターンのみに絞っています。
各項目において、 コードの 1 行目が仮引数による定義で、 2 行目以降がそれをポイントフリースタイルに書き換えた定義です。 概ね、 以下のように関数名や変数名を使い分けています。
hoge
,fuga
- そこで定義する関数
x
,y
,z
- 仮引数 (これを消したい)
f
,g
,h
- すでに別の場所で定義されている関数
s
,t
,u
- すでに別の場所で定義されている定数
#
- すでに別の場所で定義されている演算子
関数合成の応用
合成の基本
hoge x = g (f x)
hoge = g . f
全ての基本。
引数に順に関数を適用する場合は、 関数合成の演算子である .
を用いることができる。
合成する最初の関数が 2 引数以上の場合
hoge x y = g (f x y)
hoge = (g .) . f
合成する関数がともに 1 引数なら上のように .
で繋げれば良いが、 合成する最初の関数が 2 引数の場合は少し複雑になる。
hoge x y = g (x # y)
hoge = (g .) . (#)
このパターンは f
が演算子の場合も多そう。
hoge x y z = g (f x y z)
hoge = ((g .) .) . f
最初の関数が 3 引数の場合はこうなる。
4 引数以上の場合がどうなるかは省略するが、 括弧と .
が増えるだけである。
infixr 9 .^
(.^) :: (c -> d) -> (a -> b -> c) -> (a -> b -> d)
(.^) = (.) . (.)
infixr 9 .^^
(.^^) :: (d -> e) -> (a -> b -> c -> d) -> (a -> b -> e)
(.^^) = (.) . (.) . (.)
多変数関数と 1 変数関数の合成のために、 専用の演算子を用意しておくのもアリかもしれない。
2 つの値に同じ関数を適用してさらに関数を適用する
hoge x y = g (f x) (f y)
hoge = on g f
hoge = (. f) . g . f
hoge = curry $ uncurry g . f *** f
2 つの値を取ってそれぞれに同じ関数を適用し、 それらの結果を別の 2 引数関数に渡すには、 on
を使えば良い。
3 行目は関数合成だけで書いたもので、 2 つの値に適用する関数が異なっている場合にも応用が効くが、 何をしてるのか分かりづらいのでやめた方が良さそう。
4 行目は ***
とカリー化を利用したもので、 これも 2 つの値への関数が異なっている場合にも使えるが、 ちょっと冗長な気がする。
hoge x y = f x # f y
hoge = on (#) f
g
が演算子の場合も多いと思う。
引数を複数回使うもの
2 つの関数をそれぞれ適用したものをさらに関数に適用する
hoge x = f (g x) (h x)
hoge = liftA2 f g h
1 つの引数に対して 2 つの関数を別々に適用し、 それらの結果を別の 2 引数関数に渡す場合、 このように liftA2
が利用できる。
本来この関数は、 普通の二項演算を Applicative
の文脈の二項演算に持ち上げるための関数だが、 ここでは (->) r
が Applicative
のインスタンスであることを利用して、 (a -> b -> c) -> (r -> a) -> (r -> b) -> (r -> c)
型の関数として用いている。
hoge x = (g x, h x)
hoge = liftA2 (,) g h
hoge = g &&& h
特に f
がタプルを構成する (,)
の場合は、 liftA2 (,)
と書いても良いが、 &&&
を使うとさらに簡潔になる。
hoge x = (g x) (h x)
hoge = liftA2 ($) g h
hoge = ap g h
さらに f
が関数適用の場合は、 liftA2 ($)
の代わりに、 ap
が使える。
タプルの要素それぞれに関数を適用する
hoge (x, y) = (f x, g y)
hoge = f *** g
タプルの左右それぞれの要素に別々の関数を適用して、 その結果から成る新たなタプルを得ることについては、 まさにその用途のための ***
がある。
hoge (x, y) = (f x, y)
hoge = f *** id
hoge = first f
fuga (x, y) = (x, g y)
fuga = id *** g
fuga = second g
タプルの要素の一方にのみ関数を適用する場合は、 f *** id
や id *** g
などとしても良いが、 first
と second
も使える。
hoge (x, y) = h (f x) (g y)
hoge = uncurry h . f *** g
hoge = uncurry $ (. g) . h . f
応用例として、 タプルのそれぞれの要素に関数を適用して、 その結果を 2 変数関数に渡す例を挙げた。
uncurry
で 2 変数関数からタプルを取る関数に変換して、 ***
を用いて作ったタプルからタプルへの関数と合成している。
***
を用いなくても 3 行目のように書けるが、 こちらは何をしているのかパッと見で分かりづらい。
1 つの引数を複製して渡す
hoge x = f x x
hoge = join f
1 つの引数を複製して 2 引数関数に渡す場合、 join
が利用できる。
この関数は二重のモナドを一重にするもので、 ここでは (->) r
が Monad
のインスタンスであることを利用して、 (r -> r -> a) -> (r -> a)
型の関数として用いている。
hoge x = f x x x
hoge = join $ join f
3 つにコピーしたいなら join
を繋げれば良い。