web-dev-qa-db-ja.com

Haskellタイプの署名を理解する

私は自分自身にHaskellを教えている最中であり、次のタイプの署名について疑問に思っていました。

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

それをどのように解釈すればよいですか(しゃれは意図されていません)?

半類似の結果も不可解であることが証明されています。

Prelude> :t map
map :: (a -> b) -> [a] -> [b]
Prelude>
26
CaitlinG

mapから始めます。 map関数は、リスト内のすべての要素に操作を適用します。もしわたしが持っていたら

add3 :: Int -> Int
add3 x = x + 3

次に、Intを使用してこれをmapsのリスト全体に適用できます。

> map add3 [1, 2, 3, 4]
[4, 5, 6, 7]

したがって、型の署名を見ると

map :: (a -> b) -> [a] -> [b]

最初の引数は(a -> b)であることがわかります。これは、aを受け取り、bを返す関数です。 2番目の引数は[a]であり、これはタイプaの値のリストであり、戻り値のタイプ[b]は、タイプbの値のリストです。したがって、平易な英語では、map関数は値のリスト内の各要素に関数を適用し、それらの値をリストとして返します。

これがmap高階関数にするものであり、引数として関数を取り、それを処理します。 mapを確認する別の方法は、型シグネチャに括弧を追加してそれを作成することです。

map :: (a -> b) -> ([a] -> [b])

したがって、関数をaからbから[a]から[b]への関数に変換する関数と考えることもできます。


関数($)のタイプは

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

そして、のように使用されます

> add3 $ 1 + 1
5

right(この場合は1 + 1)にあるものを取得し、それをleft(ここではadd3)の関数に渡すだけです。何でこれが大切ですか?便利なfixity、つまり演算子の優先順位があり、

> add3 (1 + 1)

したがって、右側にあるものはすべて、左側に渡される前に基本的に括弧で囲まれます。これにより、複数の関数をチェーン化するのに役立ちます。

> add3 $ add3 $ add3 $ add3 $ 1 + 1

より良いです

> add3 (add3 (add3 (add3 (1 + 1))))

かっこを閉じる必要がないためです。

30
bheklilr

さて、すでに述べたように、$は、カリー化を忘れて、たとえばC++のように見れば簡単に理解できます。

template<typename A, typename B>
B dollar(std::function<B(A)> f, A x) {
  return f(x);
}

しかし実際には、これには関数を値に適用するだけではありません。 $mapの署名の間の明らかな類似性は、実際にはかなり深い圏論的意味を持っています。どちらも、ファンクターの射作用の例です。

私たちがいつも扱っているカテゴリーHaskでは、オブジェクトは型です。 ( それは少し混乱します 、しかし心配しないでください)。射は関数です。

最もよく知られている(endo-)functorsは、 同名の型クラス のインスタンスを持つものです。しかし実際には、数学的には、ファンクターはオブジェクトをオブジェクトに、射を射にマップするものにすぎません。1map(しゃれを意図したものだと思います!)は例です。オブジェクト(つまり、型)Aを取り、それを型[A]にマップします。そして、任意の2つのタイプABの場合、射(つまり関数)A -> Bを取り、タイプ[A] -> [B]の対応するリスト関数にマップします。 。

これは、ファンクタークラスの署名操作の特殊なケースです。

fmap :: Functor f   =>   (a->b) -> (f a->f b)

ただし、数学ではこのfmapに名前を付ける必要はありません。したがって、identity functorも存在する可能性があります。これは、単に任意のタイプをそれ自体に割り当てます。そして、それ自体へのすべての射:

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

「アイデンティティ」は明らかにより一般的に存在し、任意のタイプの値をそれ自体にマップすることもできます。

id :: a -> a
id x = x

そして確かに、可能な実装は

($) = id

1オブジェクトと射をマッピングするanythingはファンクターです... ファンクターの法則 を満たす必要があります。

7
leftaroundabout

($)は単なる関数適用です。型a->bの関数、型aの引数を取得し、関数を適用して、型bの値を返します。

mapは、関数型のシグネチャを読み取ることがそれを理解するのにどのように役立つかを示すすばらしい例です。 mapの最初の引数はaを取り、bを返す関数であり、2番目の引数はタイプ[a]のリストです。したがって、mapはタイプa->bの関数をa値のリストに適用します。そして、結果の型は確かに型[b]-b値のリストです!

(a->b)->[a]->[b]は、「関数とリストを受け入れて別のリストを返す」、および「タイプa->bの関数を受け入れてタイプ[a]->[b]の別の関数を返す」と解釈できます。このように見ると、map "upgrade" f(このコンテキストでは「lift」という用語がよく使用されます)はリストで機能します。doubleが整数を2倍にする関数の場合、次にmap doubleは、リスト内のすべての整数を2倍にする関数です。

6
Benesh