Haskell Language
高次関数
サーチ…
備考
高次関数は、関数をパラメータとして受け取り、関数をその戻り値として返す関数です。
高次関数の基礎
続行する前に部分アプリケーションを確認してください。
Haskellでは、引数や関数を返す関数を上位関数と呼びます 。
以下はすべて高次関数です:
map :: (a -> b) -> [a] -> [b]
filter :: (a -> Bool) -> [a] -> [a]
takeWhile :: (a -> Bool) -> [a] -> [a]
dropWhile :: (a -> Bool) -> [a] -> [a]
iterate :: (a -> a) -> a -> [a]
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
scanr :: (a -> b -> b) -> b -> [a] -> [b]
scanl :: (b -> a -> b) -> b -> [a] -> [b]
これらは、関数を引数として他の関数に渡すことで、すでに持っている関数の上に新しい関数を作成できるという点で、特に便利です。したがって、名前、 高次関数 。
検討してください:
Prelude> :t (map (+3))
(map (+3)) :: Num b => [b] -> [b]
Prelude> :t (map (=='c'))
(map (=='c')) :: [Char] -> [Bool]
Prelude> :t (map zipWith)
(map zipWith) :: [a -> b -> c] -> [[a] -> [b] -> [c]]
この機能を簡単に作成する能力(例えば、ここで使用されている部分的なアプリケーションなど)は、関数型プログラミングを特に強力にする機能の1つで、他の言語では数十行も要する短く洗練されたソリューションを導き出すことができます。たとえば、次の関数は、2つのリストに整列された要素の数を返します。
aligned :: [a] -> [a] -> Int
aligned xs ys = length (filter id (zipWith (==) xs ys))
ラムダ式
ラムダ式は、他の言語の匿名関数に似ています。
ラムダ式は、束縛されるべき変数を指定するオープン式である。評価(関数呼び出しの値を見つける)は、ラムダ式の本体のバインドされた変数をユーザが指定した引数で置き換えることで実現されます。簡単に言えば、ラムダ式は変数の束縛と置換によって関数を表現することを可能にします。
ラムダ式は次のようになります
\x -> let {y = ...x...} in y
ラムダ式の中で、矢印の左側の変数は、右側、すなわち関数の本体にバインドされているとみなされます。
数学的関数を考える
f(x) = x^2
ハスケルの定義として、
f x = x^2
f = \x -> x^2
これは関数f
がラムダ式\x -> x^2
と等価であることを意味する。
高次関数map
のパラメータ、つまりa -> b
関数を考えてみましょう。それがmap
内の呼び出しで一度だけ使用され、プログラム内のどこにも使用されない場合、そのような使い捨て関数の名前を付ける代わりにラムダ式として指定すると便利です。ラムダ式として書かれているが、
\x -> let {y = ...x...} in y
x
型の値保持a
、 ...x...
変数を参照するHaskellの表現でありx
、およびy
式の値保持b
。たとえば、次のように書くことができます
map (\x -> x + 3)
map (\(x,y) -> x * y)
map (\xs -> 'c':xs) ["apples", "oranges", "mangos"]
map (\f -> zipWith f [1..5] [1..5]) [(+), (*), (-)]
カッシング
Haskellでは、すべての関数がカリー化されているとみなされます。すなわち、Haskellのすべての関数はただ1つの引数をとります。
関数div
取ってみましょう:
div :: Int -> Int -> Int
この関数を6と2で呼び出すと、意外にも3が得られます。
Prelude> div 6 2 3
しかし、これは私たちが考えるかもしれない方法では全く動作しません。最初のdiv 6
が評価され、 Int -> Int
型の関数が返されます 。この結果得られた関数は、次に値3に適用される値2に適用されます。
関数の型シグニチャを見ると、「 Int
型の2つの引数を取る」から「 Int
を取り、 Int
をとる関数を返す」という考え方に変わることがあります。これは、タイプ表記の矢印が右に関連付けられていると考えると再確認されます。したがって、 div
は実際には次のように読み取られます。
div :: Int -> (Int -> Int)
一般的に、ほとんどのプログラマは、少なくとも言語を学んでいる間はこの動作を無視できます。 理論的な観点からは、「すべての関数が一様に扱われると、正式な証明がより簡単になります(1つの引数で、1つの結果が出ます)」。