29

Haskellの型推論には、少し学習曲線があります(控えめに言っても!)。それを学び始める良い方法は、簡単な例を使うことです。したがって、以下は型推論の「HelloWorld」のビットです。

次の例を考えてみましょう。

Prelude> :t 3
3 :: (Num t) => t
Prelude> let x = 3
Prelude> :t x
x :: Integer

したがって、問題は次のとおりです。3とxのタイプが異なるのはなぜですか。

リンクの概要:

完全なストーリーについては、以下の回答をお読みください。ここにリンクの要約があります:

  1. GHCタイプのデフォルト:Haskellレポートセクション4.3.4
  2. GHCiの拡張タイプのデフォルト:GHCiセクション2.4.5の使用
  3. 単形制限:Haskell wiki
4

3 に答える 3

32

ここには、acfoltzerに含まれるいくつかのリンクで言及されている別の要因がありますが、ここで明示する価値があるかもしれません。単相制限の影響に遭遇しています。あなたが言う時

let x = 5

変数のトップレベルの定義を作成します。MRは、そのような定義は、型シグネチャを伴わない場合、未解決の型変数に(うまくいけば)適切なデフォルトインスタンスを選択することにより、単形値に特化する必要があると主張しています。対照的に、推論されたタイプを要求するために使用する場合、そのような制限やデフォルトは課されません。それで:t

> :t 3
3 :: (Num t) => t

3は実際にオーバーロードされているためです。任意の数値タイプで許可されます。デフォルトのルールIntegerはデフォルトの数値タイプとして選択するため、

> let x = 3
> :t x
x :: Integer

しかし、今度はMRをオフにしましょう。

> :set -XNoMonomorphismRestriction
> let y = 3
> :t y
y :: (Num t) => t

MRがない場合、定義は可能な限り多形であり、と同じようにオーバーロードされ3ます。チェックしてるだけ...

> :t y * (2.5 :: Float)
y * (2.5 :: Float) :: Float
> :t y * (3 :: Int)
y * (3 :: Int) :: Int

多型は、関連するインスタンスで提供されるメソッドy = 3に応じて、これらの用途に異なる方法で特化されていることに注意してください。つまり、の特定の表現に関連付けられているのではなく、の表現を構築するためのスキームに関連付けられています。素朴に編集された、それはスローのレシピであり、MRの動機として一部の人々が引用しています。fromIntegerNumy33

私は(地元ではそういうふりをして)、単相性の制限がより小さな悪なのか、より大きな悪なのかについての議論に中立です。私は常にトップレベルの定義の型署名を書いているので、私が達成しようとしていることについて曖昧さはなく、MRは重要ではありません。

型システムがどのように機能するかを学ぼうとするとき、型推論の側面を分離することは本当に役に立ちます。

  1. 「計画に従ってください」、特定のユースケースに多態的な定義を特化します。制約解決のかなり堅牢な問題であり、バックチェーンによる基本的な統合とインスタンスの解決が必要です。と

  2. 「計画を推測する」、型を一般化して、型シグネチャのない定義にポリモーフィック型スキームを割り当てます。これは非常に脆弱であり、型クラス、上位のポリモーフィズム、 GADT、見知らぬものになります。

最初のものがどのように機能するかを学び、2番目のものが難しい理由を理解するのは良いことです。型推論の奇妙さの多くは、2番目の問題、およびあいまいさに直面したときに有用なデフォルトの動作を提供しようとする単相制限などのヒューリスティックに関連しています。

于 2011-08-14T09:00:26.220 に答える
6

これは、GHCiでのタイプのデフォルトが原因で発生します。これについては、ここここここ、およびここで説明します。残念ながら、これは検索が難しいもののように思われます。「タイプのデフォルト」というフレーズを知る前に、この動作を説明する方法がたくさんあるからです。

更新:D'oh。悪い例を削除しました。

于 2011-08-14T05:42:35.613 に答える
4

単射制限がある理由については誰も言及していないので、 A History of Haskell:Being LazyWithClassからこのビットを追加すると思いました。

6.2単相制限初期の論争の主な原因は、いわゆる「単相制限」でした。genericLengthに次のオーバーロードされたタイプがあるとします。

genericLength :: Num a => [b] -> a 

ここで、この定義について考えてみましょう。

f xs = (len, len) 
     where len = genericLength xs 

len1回だけ計算する必要があるように見えますが、実際には2回計算できます。なんで?タイプを推測できるから len :: (Num a) => aです; 辞書を渡す翻訳で脱糖されると、lenの出現ごとに1回呼び出される関数になりlen、それぞれが異なるタイプで使用される可能性があります。

ヒューズは、この方法で計算を黙って複製することは容認できないと強く主張しました。彼の議論は、彼が書いたプログラムが予想よりも指数関数的に遅くなることによって動機付けられました。(これは確かに非常に単純なコンパイラーでしたが、コンパイラーの最適化に応じて、これほどパフォーマンスの違いを大きくすることには消極的でした。)

多くの議論の後、委員会は今では悪名高い単相制限を採用しました。簡単に言うと、関数のように見えない(つまり、左側に引数がない)定義は、オーバーロードされた型変数では単形でなければならないということです。この例では、ルールlenは両方のオカレンスで同じタイプで使用されるように強制します。これにより、パフォーマンスの問題が解決されます。lenプログラマーは、ポリモーフィックな動作が必要な場合に備えて、明示的な型シグネチャを提供できます。

単相性の制限は明らかに言語に対する疣贅です。予期しない、またはあいまいなエラーメッセージを表示することにより、すべての新しいHaskellプログラマーを噛むようです。代替案については多くの議論がありました。Glasgow Haskellコンパイラ(GHC、セクション9.1)はフラグを提供します:

-fno-monomorphism-restriction

制限を完全に抑制します。しかし、この間ずっと、真に満足のいく代替案は進化していません。

単相制限に対する論文のトーンは非常に興味深いと思います。

于 2012-10-19T17:37:34.833 に答える