9

私はHaskellの初心者で、推論された型などがどのように機能するかを理解するのが難しいです。

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

それは正確にはどういう意味ですか?

foldr :: (a -> b -> b) -> b -> [a] -> b
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl1 :: (a -> a -> a) -> [a] -> a

これらの違いは何ですか?

そして、推論されたタイプの

foldr map[a] -> [a -> a] -> [a]

しかし、それはなぜですか?

ありがとう!

4

4 に答える 4

10

あなたが例を取るなら

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

これは、マップが2つの引数を取ることを意味します

  1. タイプaからタイプbへの関数
  2. タイプaのリスト

そしてそれは戻ります

  1. bのリスト

すでにここでパターンを見ることができますが、「a」と「b」を置き換えるとより明確になります

  • a=文字列
  • b = Int

したがって、このタイプの定義は次のようになります。

map :: (String -> Int) -> [String] -> [Int]

これで、文字列を受け取ってIntを返す関数と、文字列のリストを受け取ってIntのリストを返す関数になりました。

文字列を受け取って戻り、Intがである関数を考えてみましょうreadread指定した文字列を使用して、別の文字列に変換します。ここではIntコンテキストに配置しているため、文字列をintに変換します

map read ["1", "2", "112", 333"]

これにより、

[1, 2, 112, 333]

map関数readを受け取り、それをリストのすべての要素にマップ(適用)するからです。あなたはそれがあなたのためにそれを世話するので、、またはreadのような議論を言う必要はありません。read "1"read nmap

そして、それが本当にすべてです。関数の型は、どの型を取り、どの型を返すかだけを示します。もちろん、カリー化もありますが、わざとかどうかにかかわらず、後でそれを取得します!これは基本的に、関数が多くの引数をとらず、1つだけをとることを意味します。関数を実行するとします+。を評価する1+2と、に追加された別の数値を受け取る関数が返されます。1ここに別の数値があるため2、それを使用します。あなたはそれをとして評価し、それ(1+)を渡すことができたかもしれません、おそらくしばらくして番号を追加しました。のインフィックス構文がない場合は、より明確になります+。書かれている可能性が(+) 1 2あるので、最初にこのステートメントは次のようになります(+) 1、これは型です(簡略化されています!ここではNum型クラスを紹介しません)Int -> Int。したがって(+) 1、(または(1+)そのことについては)値を適用できる別の関数であり、その時点で結果は3に計算できるようになります。

これが実際にどのように見えるかを示します:((Num a) =>混乱する場合は、ここの部分を無視してください。これは後で詳しく説明する概念です。必要に応じて、aここのタイプを置き換えてInt、部分を完全に無視して(Num a) =>ください。)

 (+)  :: (Num a) => a -> a -> a
 (+2) :: (Num a) => a -> a
(1+)  :: (Num a) => a -> a
(1+2) :: (Num a) => a

Prelude> (+2) 5
7
Prelude> map (+3) [1,2,3]
[4,5,6]

そして2番目の質問に:あなたは推論された型をまったく定義しません。コンパイラー/インタープリターが明示的に名前を付けずに型自体を「推論」(読み取り:計算)するため、これらは「推論」と呼ばれます。

違いについてfoldX:それらはすべてまったく同じことをします:リストを単一の値に減らします。関数間の違いは、単に内部定義です。foldlリストを左から折り、foldr右に折ります。

したがって、リストを要約すると、これらすべてを次のように使用できます...

foldl1 (+) [1,2,3] == 6
foldr (+) 0 [1,2,3] == 6
foldl (+) 0 [1,2,3] == 6

foldl1を除いて、折りたたむ関数、開始値(アキュムレータ)、および折りたたむリストを指定します。fold1それはそれ自身のアキュムレータを持っています、あなたはそれにあなた自身のものを与えません。

実際には、スタックオーバーフロー(tee、hee)によってクラッシュすることなく、BIGリストに適しているfoldrため、を使用する方が適切です。foldそして、このルールの例外はfoldl'( "'"!に注意してください!)Data.Map-これは厳密な折り畳みであり、大きなリストにも適しています。

関数に自分で型を指定したい場合(作成した場合)、次の例を検討してください。

double :: Int -> Int
double n = 2 * n

または面倒な方法で

double :: Int -> Int
double = (*2)

両方の例の最初の行(double :: Int -> Int)は、探しているものです。これは、コンパイラーまたはインタープリターに関数を識別させる方法です。その場合は省略でき、コンパイラー/インタープリターはそれらを検出します(場合によっては、最初に考えたよりも優れた方法で)。

于 2010-05-11T19:06:24.173 に答える
3

Haskellの関数は、カリー化と呼ばれる表記法を使用します。次のような定義

plus :: Int -> Int -> Int

plusこれは、2つのsを取りInt、右端の型の値を返す関数であることを意味します(これも同様ですInt)。より詳細には、カリー化とは、部分的に適用された関数を使用して作業することを意味します。これにより、次のようなコードを記述できます。

plus1 :: Int -> Int
plus1 = plus 1

型シグニチャの小文字の識別子は、あらゆる可能な型のプレースホルダーと見なすことができる、いわゆる型変数を示します。

定義

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

したがって、は、任意のタイプaとについてbmap関数fromと'のリストを取り、そこaからb'のaリストを生成することを意味しbます。

例を見てみましょう

map show [1, 2, 3]

ご覧のとおり、指定されたリストはのリストなIntので、a = Int。さらに、を返す各要素にmap適用する必要があります。定義から型に適合しなければならないので、それを推測することができます。showStringshowa -> bb = String

式はを返す[b]ので、結果は。になり[String]ます。

このような署名をさらに明確にするために、より冗長な構文を使用して署名を記述できます。

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

これmapで、すべてのタイプaとで機能することになっていることが明示的に示されbます。


折り目については、それらの定義を見てください。上記の知識があれば、関数がをするのかが明確な場合、型アノテーションは自明である必要があります。

例:factorial n = foldl (*) 1 [1..n]

の署名はにfoldr mapなります。

foldr map :: [a] -> [a -> a] -> [a]

:t foldr mapHaskellインタープリター(GHCi)に入力するだけで、これらを取得することもできます。

于 2010-05-11T19:08:43.983 に答える
1

Haskellでは、小文字の変数は型変数です。一方、Char単にChar、、aまたはbを意味する可能性があります、、Charまたは、BoolまたはInteger他のタイプを意味します。重要なのは、すべてaが同じタイプになるということです。そして、すべてbが同じタイプになります。

たとえばmap、最初の引数として、あるタイプの変数を受け取り、a別のタイプの変数を返す関数を取りますb。次にmap、タイプのリストを受け取る新しい関数を返しますa。この新しい関数は、タイプのリストを返しますb

increment整数に1を加算する関数があるとします。map increment [1,2,3]最終的にリストを返します[2,3,4]。

型変数が置き換えられた以前の定義は次のmapようになります。

map :: increment -> [1,2,3] -> [2,3,4]

そしてincrement、ところで、次のようになります

increment :: Integer -> Integer

面白く書かれているのは、関数を部分的に適用できるからです。ここでは、マップの部分的なアプリケーションに基づいて新しい関数を作成します。

incrementList = map increment

その後、あなたは使用することができます

incrementList [1,2,3]

同じ結果になります

于 2010-05-11T19:04:02.027 に答える
1

型宣言の小文字は型変数です。これは、これがある場合、aこれは同じタイプでなければならないことを意味します。

括弧で囲まれた(例(a -> b))部分式は、関数がそのパラメーターの1つとして関数を受け入れることを意味する傾向があります。

角かっこは何かのリストを示します。

したがってmap、これらの引数を受け入れます。

  • (a -> b)1つの引数を受け入れる関数
  • [a]最初の引数関数が作用できるリスト

[b]結果としてリストを作成します。

(.)同じ方法でデコードできます。

  • (a -> b)単一の引数を受け入れる関数です
  • (c -> a)最初の関数の引数と同じtypaを生成する関数です
  • c2番目の関数が使用するのと同じタイプの引数

ただし、Haskellはきちんとしたことを行うことができます。つまり、引数を省略すると、残りのシグネチャを持つ関数が返されます。したがって(.)、関数をチェーンするために一般的に使用されます。

ord :: Char -> Int文字を受け取り、その整数表現を返し、add32 :: Int -> Intその引数に32を追加する2つの関数があるとします。

関数を書くときupcaseVal = add32 . ord、upcaseValは署名を持っています。upcaseVal :: Char -> Intこれは、(.)関数がそれを残していてc -> b、関数定義でそれらを置き換えるためです。

したがってfoldr map、次のようにする必要があります。

  1. foldrの最初の引数は、2つの引数を取る関数mapです。
  2. (a -> b -> b関数の出力は2番目の引数( )と同じタイプである必要があるため、次のようになります。map a = b

したがって、最終的なタイプは次のようになります。

(b -> b) -> [b] -> [b] -> b -> [b] -> b

しかし、コンパイラはほとんどの場合このようなものを処理するので、それほど気にする必要はないと思います。

于 2010-05-11T19:06:41.553 に答える