GGG

プログラミング言語やソフトウェア開発について思ったことを書いてます

Haskell 最初のn歩 :関数適用($) と関数合成(.)

 関数合成(.)と関数適用($)について

パッと見では違いがよく分からない。何が違うのだろう?

と思ったので実際に動かして確認してみた。

 

[1..100] に tail を適用し、 (リストを受け取り、2個目以降をリストで返す)

その結果にsum を適用し、( リストを受け取り、その合計を算出する )

その結果に negateを適用する。 ( 数値を受け取り、 符号を反転する )

この流れを例にしてみる。

Prelude> negate ( sum ( tail [1..100] ) )
-5049

 

($)関数:関数適用演算子

($)関数は右結合。

($)関数は優先順位が低め。(半角空白による関数適用よりも低い)

用途:括弧()の数を減らして可読性UP

 

関数書式を見てみる。

Prelude> :t ($)
($) :: (a -> b) -> a -> b

 

 実行してみる。

  • 失敗

Prelude> (negate $ sum $ tail) [1..100]

<interactive>:73:17:
Couldn't match expected type ‘[[t0] -> t]’
with actual type ‘[a0] -> [a0]’
Relevant bindings include it :: t (bound at <interactive>:73:1)
Probable cause: ‘tail’ is applied to too few arguments
In the second argument of ‘($)’, namely ‘tail’
In the second argument of ‘($)’, namely ‘sum $ tail’

 

  •  成功

Prelude> negate $ sum $ tail [1..100]
-5049 

 

イメージではC#LINQを左側からではなく、右側から適用していくように見える。

 関数適用した結果の書式を見てみる。

Prelude> :t ( negate $ sum $ tail )

<interactive>:1:18:
Couldn't match expected type ‘[s]’ with actual type ‘[a0] -> [a0]’
Probable cause: ‘tail’ is applied to too few arguments
In the second argument of ‘($)’, namely ‘tail’
In the second argument of ‘($)’, namely ‘sum $ tail’

 

どうやら tail に[]の引数がないため 怒られたみたい。

引数に[1..] を付加してリトライしてみる。

Prelude> :t ( negate $ sum $ tail [1..])
( negate $ sum $ tail [1..]) :: (Num s, Enum s) => s

 

 

.関数:関数合成演算子

(.)関数は右結合。

(.)関数は数学の関数合成を表現するもののようだ。

数学の関数合成で使う記号∘を (.) で表現する。

(f ∘ g)(x) = (f ( g x ) )   

(.)演算子は優先順位が低め。(半角空白による関数適用よりも低い)

関数書式を見てみる。

Prelude> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

実行してみる。

  • 失敗

Prelude> negate . sum . tail [1..100]

<interactive>:67:16:
Couldn't match expected type ‘a -> [c]’ with actual type ‘[a0]’
Relevant bindings include
it :: a -> c (bound at <interactive>:67:1)
Possible cause: ‘tail’ is applied to too many arguments
In the second argument of ‘(.)’, namely ‘tail [1 .. 100]’
In the second argument of ‘(.)’, namely ‘sum . tail [1 .. 100]’

  •  成功 

Prelude> (negate . sum . tail) [1..100]
-5049

 

関数適用した結果の書式をみてみる

Prelude> :t ( negate . sum . tail )

( negate . sum . tail ) :: Num c => [c] -> c

 

違い

目的が違うのだろうけど、操作を中心に見てみる。

  • ($)関数

()で書ける操作を簡略化することが目的のひとつ?。

操作としては右側から順に関数適用していく。

実行できる状態になっている必要があり、引数も含めて用意が必要なのかもしれない。

 

($)関数は、半角空白(関数適用の区切り)よりも優先順位が低いため

 F、G、Hが関数だとすると

 F $ G $ H [1..] と書くと

   F $ G $ (H [1..] ) と同じ意味になると考えられる。

  そもそも F ( G ( H [1..] ) ) を簡略化し、 F $ G $ H [ 1.. ] と表記することが目的であるため、問題ない。

 

 

  • (.)関数

複数の関数を合成して、一つの関数を作ることが目的。

 

そのため、合成した関数を一つの関数として適用する場合は()括弧で合成した関数を囲って引数を適用する必要がある。数学の関数合成の定義のように。

 

 (.)関数は、半角空白(関数適用の区切り)よりも優先順位が低いため

 F、G、Hが関数だとすると

 F . G . H [1..] と書くと

   F . G . (H [1..] ) と同じ意味になると考えられる。

  H [1..] の結果は関数ならもしかしたら意図しない動作をするのかもしれないが

  例ではtail関数であるため、返り値は リストであるため書式エラーでNGとなりコンパイルエラーとなる。

 

理屈も大事だが、実際に動かして確認してみた。 

 

参考

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!