83

Haskell コンパイラが、たとえばポイントフリー定義を使用している場合など、予想よりもポリモーフィックではない型を推論することがあることに戸惑っています。

問題は、古いバージョンのコンパイラではデフォルトでオンになっている「モノモーフィズムの制限」にあるようです。

次の haskell プログラムを検討してください。

{-# LANGUAGE MonomorphismRestriction #-}

import Data.List(sortBy)

plus = (+)
plus' x = (+ x)

sort = sortBy compare

main = do
  print $ plus' 1.0 2.0
  print $ plus 1.0 2.0
  print $ sort [3, 1, 2]

これをコンパイルするghcと、エラーは発生せず、実行可能ファイルの出力は次のようになります。

3.0
3.0
[1,2,3]

mainボディを次のように変更した場合:

main = do
  print $ plus' 1.0 2.0
  print $ plus (1 :: Int) 2
  print $ sort [3, 1, 2]

コンパイル時エラーは発生せず、出力は次のようになります。

3.0
3
[1,2,3]

予想通り。ただし、次のように変更しようとすると:

main = do
  print $ plus' 1.0 2.0
  print $ plus (1 :: Int) 2
  print $ plus 1.0 2.0
  print $ sort [3, 1, 2]

タイプエラーが発生します:

test.hs:13:16:
    No instance for (Fractional Int) arising from the literal ‘1.0’
    In the first argument of ‘plus’, namely ‘1.0’
    In the second argument of ‘($)’, namely ‘plus 1.0 2.0’
    In a stmt of a 'do' block: print $ plus 1.0 2.0

sort異なる型で 2 回呼び出そうとすると、同じことが起こります。

main = do
  print $ plus' 1.0 2.0
  print $ plus 1.0 2.0
  print $ sort [3, 1, 2]
  print $ sort "cba"

次のエラーが発生します。

test.hs:14:17:
    No instance for (Num Char) arising from the literal ‘3’
    In the expression: 3
    In the first argument of ‘sort’, namely ‘[3, 1, 2]’
    In the second argument of ‘($)’, namely ‘sort [3, 1, 2]’
  • それがポリモーフィックではなく、引数が必要ghcだと突然考えるのはなぜですか? への唯一の参照はのアプリケーションにあります。定義が明らかに多態的である場合、それはどのように問題になるのでしょうか?plusIntIntplus
  • 突然インスタンスが必要ghcだと思うのはなぜですか?sortNum Char

さらに、関数定義を独自のモジュールに配置しようとすると、次のようになります。

{-# LANGUAGE MonomorphismRestriction #-}

module TestMono where

import Data.List(sortBy)

plus = (+)
plus' x = (+ x)

sort = sortBy compare

コンパイル時に次のエラーが発生します。

TestMono.hs:10:15:
    No instance for (Ord a0) arising from a use of ‘compare’
    The type variable ‘a0’ is ambiguous
    Relevant bindings include
      sort :: [a0] -> [a0] (bound at TestMono.hs:10:1)
    Note: there are several potential instances:
      instance Integral a => Ord (GHC.Real.Ratio a)
        -- Defined in ‘GHC.Real’
      instance Ord () -- Defined in ‘GHC.Classes’
      instance (Ord a, Ord b) => Ord (a, b) -- Defined in ‘GHC.Classes’
      ...plus 23 others
    In the first argument of ‘sortBy’, namely ‘compare’
    In the expression: sortBy compare
    In an equation for ‘sort’: sort = sortBy compare
  • ghcに多相型を使用できOrd a => [a] -> [a]ないのはなぜsortですか?
  • そして、なぜghc扱いplusplus'異なるのですか?plusはポリモーフィック型である必要があり、Num a => a -> a -> aこれが の型とどのように異なるのかはよくわかりませんが、エラーが発生するsortだけです。sort

最後に:sortファイルの定義にコメントを付けるとコンパイルされます。ただし、それをロードしてghci取得するタイプを確認すると、次のようになります。

*TestMono> :t plus
plus :: Integer -> Integer -> Integer
*TestMono> :t plus'
plus' :: Num a => a -> a -> a

plusポリモーフィックの型ではないのはなぜですか?


これは、 meta question で説明されているように、Haskell でのモノモーフィズムの制限に関する正規の質問です。

4

1 に答える 1

111

単型性制限とは何ですか?

Haskell wiki で述べ​​られているモノモーフィズムの制限は次のとおりです。

Haskell の型推論における直感に反するルール。型シグネチャを提供するのを忘れた場合、この規則は「型のデフォルト設定」規則を使用して自由型変数を特定の型で満たすことがあります。

これが意味することは、状況によっては、型があいまいである (つまり、ポリモーフィックである) 場合、コンパイラはその型をあいまいではないものにインスタンス化することを選択するということです。

どうすれば修正できますか?

まず第一に、いつでも型シグネチャを明示的に提供でき、これにより制限のトリガーが回避されます。

plus :: Num a => a -> a -> a
plus = (+)    -- Okay!

-- Runs as:
Prelude> plus 1.0 1
2.0

あるいは、関数を定義している場合は、ポイントフリー スタイルを避ける ことができます。たとえば、次のように記述します。

plus x y = x + y

オフにする

制限を修正するためにコードに何もする必要がないように、単に制限をオフにすることができます。動作は 2 つの拡張機能によって制御され MonomorphismRestrictionます。有効にする (デフォルト) 一方で、 NoMonomorphismRestriction無効にします。

ファイルの一番上に次の行を追加できます。

{-# LANGUAGE NoMonomorphismRestriction #-}

GHCi を使用している場合は、次の:setコマンドを使用して拡張機能を有効にできます。

Prelude> :set -XNoMonomorphismRestriction

ghcコマンドラインから拡張機能を有効にするように指示することもできます。

ghc ... -XNoMonomorphismRestriction

注:コマンドライン オプションで拡張機能を選択するよりも、最初のオプションを優先する必要があります。

この拡張機能と他の拡張機能の説明については、GHC のページを参照してください。

完全な説明

単型性制限とは何か、なぜそれが導入されたのか、どのように動作するのかを理解するために知っておく必要があるすべてを以下に要約します。

次の簡単な定義を取ります。

plus = (+)

+のすべての出現をで置き換えることができると思うでしょうplus。特に(+) :: Num a => a -> a -> aplus :: Num a => a -> a -> a.

残念ながら、そうではありません。たとえば、GHCi で次のことを試してみると:

Prelude> let plus = (+)
Prelude> plus 1.0 1

次の出力が得られます。

<interactive>:4:6:
    No instance for (Fractional Integer) arising from the literal ‘1.0’
    In the first argument of ‘plus’, namely ‘1.0’
    In the expression: plus 1.0 1
    In an equation for ‘it’: it = plus 1.0 1

:set -XMonomorphismRestriction 新しい GHCi バージョンでは必要になるかもしれません。

実際、 の型plusが期待したものではないことがわかります。

Prelude> :t plus
plus :: Integer -> Integer -> Integer

何が起こったかというと、コンパイラは が多相型であるtype をplus持っていることを認識したということです。Num a => a -> a -> aさらに、上記の定義は後で説明するルールに該当する場合があるため、彼は型 variableをデフォルトaにすることで型をモノモーフィックにすることにしました。デフォルトはIntegerご覧の通りです。

を使用して上記のコードをコンパイルghcしようとしても、エラーは発生しないことに注意してください。これは、インタラクティブな定義を処理する方法ghci(および処理する必要がある) によるものです。基本的に、以下を検討する前に、入力されたすべてのステートメントを完全に型チェックするghci必要があります。つまり、すべてのステートメントが個別の モジュールにあるかのようです。後で、なぜこれが重要なのかを説明します。

他の例

次の定義を考慮してください。

f1 x = show x

f2 = \x -> show x

f3 :: (Show a) => a -> String
f3 = \x -> show x

f4 = show

f5 :: (Show a) => a -> String
f5 = show

これらの関数はすべて同じように動作し、同じ型、つまり : の型を持つことが期待されshowますShow a => a -> String

しかし、上記の定義をコンパイルすると、次のエラーが発生します。

test.hs:3:12:
    No instance for (Show a1) arising from a use of ‘show’
    The type variable ‘a1’ is ambiguous
    Relevant bindings include
      x :: a1 (bound at blah.hs:3:7)
      f2 :: a1 -> String (bound at blah.hs:3:1)
    Note: there are several potential instances:
      instance Show Double -- Defined in ‘GHC.Float’
      instance Show Float -- Defined in ‘GHC.Float’
      instance (Integral a, Show a) => Show (GHC.Real.Ratio a)
        -- Defined in ‘GHC.Real’
      ...plus 24 others
    In the expression: show x
    In the expression: \ x -> show x
    In an equation for ‘f2’: f2 = \ x -> show x

test.hs:8:6:
    No instance for (Show a0) arising from a use of ‘show’
    The type variable ‘a0’ is ambiguous
    Relevant bindings include f4 :: a0 -> String (bound at blah.hs:8:1)
    Note: there are several potential instances:
      instance Show Double -- Defined in ‘GHC.Float’
      instance Show Float -- Defined in ‘GHC.Float’
      instance (Integral a, Show a) => Show (GHC.Real.Ratio a)
        -- Defined in ‘GHC.Real’
      ...plus 24 others
    In the expression: show
    In an equation for ‘f4’: f4 = show

したがってf2f4コンパイルしないでください。さらに、GHCi でこれらの関数を定義しようとすると、エラーは発生しませんがf2、 andの型f4() -> String!

モノモーフィズムの制限は、モノモーフィック型を作成f2および必要とするものであり、とf4の間の異なる動作は、異なる デフォルト規則によるものです。ghcghci

それはいつ起こりますか?

レポートで定義されているように、Haskellには2 つの異なるタイプのバインディングがあります。関数バインディングとパターン バインディング。関数バインディングは、関数の定義に他なりません。

f x = x + 1

構文は次のとおりです。

<identifier> arg1 arg2 ... argn = expr

モジュロガードとwhere宣言。しかし、それらは実際には問題ではありません。

少なくとも 1 つの引数が必要です。

パターン バインディングは、次の形式の宣言です。

<pattern> = expr

繰り返しますが、モジュロ ガードです。

変数は patternであるため、バインディングは次のようになることに注意してください。

plus = (+)

パターンバインディングです。pattern plus(変数) を expression にバインドしています(+)

パターン バインディングが変数名のみで構成される場合、 単純なパターン バインディングと呼ばれます。

単一型の制限は単純なパターン バインディングに適用されます。

正式には、次のように言う必要があります。

宣言グループは、相互に依存するバインディングの最小限のセットです。

レポートのセクション 4.5.1 。

次に (レポートのセクション 4.5.5 ):

次の場合にのみ、特定の宣言グループが制限されません。

  1. グループ内のすべての変数は、関数バインディング (例f x = x) または単純なパターン バインディング (例plus = (+); セクション 4.4.3.2 )によってバインドされます。

  2. 単純なパターン バインディングによってバインドされたグループ内のすべての変数に対して、明示的な型シグネチャが与えられます。(例plus :: Num a => a -> a -> a; plus = (+))。

私が追加した例。

したがって、制限された宣言グループは、 非単純なパターン バインディング ((x:xs) = f somethingや など(f, g) = ((+), (-))) があるか、型シグネチャのない単純なパターン バインディング ( など) があるグループplus = (+)です。

単相性の制限は、制限された宣言グループに影響します。

ほとんどの場合、相互再帰関数を定義しないため、宣言グループは単なるバインディングになります。

それは何をするためのものか?

単形性制限は、レポートのセクション 4.5.5 で 2 つの規則によって説明されています。

最初のルール

Hindley-Milner のポリモーフィズムに関する通常の制限は、環境内で自由に発生しない型変数のみを一般化できるというものです。さらに、制限された宣言グループの制約された型変数は、そのグループの一般化ステップで一般化されない場合があります。 (型変数が何らかの型クラスに属さなければならない場合、型変数は制約されることを思い出してください。セクション 4.5.2 を参照してください。)

強調表示されている部分は、単型性制限が導入するものです。型がポリモーフィック (つまり、型変数を含む) あり、その型変数が制約されている (つまり、クラス制約がある: たとえば、型Num a => a -> a -> aが含まれているためポリモーフィックであり、制約がaあるためa制約さNumれている) ことを示します。 .) その場合、一般化することはできません。

簡単に言えば、一般化しないということは、関数の使用plusによってその型が変わる可能性があることを意味します。

定義がある場合:

plus = (+)

x :: Integer
x = plus 1 2

y :: Double
y = plus 1.0 2

次に、型エラーが発生します。コンパイラがの宣言でplusan を介して呼び出されていることを確認すると、型変数がと統合されるため、 の型は次のようになります。IntegerxaIntegerplus

Integer -> Integer -> Integer

しかし、その後、 の定義を型チェックすると、 が引数に 適用され、型が一致しないyことがわかります。plusDouble

plusエラーが発生することなく引き続き使用できることに注意してください。

plus = (+)
x = plus 1.0 2

この場合、 の型plusは最初に であると推測されますが、制約が必要Num a => a -> a -> a な の定義で使用すると 、 に変更されます。x1.0FractionalFractional a => a -> a -> a

根拠

レポートは次のように述べています。

ルール 1 が必要な理由は 2 つありますが、どちらもかなり微妙です。

  • ルール 1 は、計算が予期せず繰り返されるのを防ぎます。たとえば、genericLength( library 内の) 標準関数でありData.List、その型は

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

    ここで、次の式を考えてみましょう。

      let len = genericLength xs
      in (len, len)
    

    len1 回だけ計算する必要があるように見えますが、ルール 1 がないと、2 つの異なるオーバーロードのそれぞれで 1 回ずつ、2 回計算される可能性があります。 プログラマーが実際に計算を繰り返したい場合は、明示的な型シグネチャを追加できます。

      let len :: Num a => a
          len = genericLength xs
      in (len, len)
    

この点については、wikiの例の方がわかりやすいと思います。関数を考えてみましょう:

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

lenがポリモーフィックである場合、タイプは次のfようになります。

f :: Num a, Num b => [c] -> (a, b)

したがって、タプルの 2 つの要素は、(len, len)実際には 異なる値になる可能性があります。しかし、これは、2 つの異なる値を取得するために、 で行われる計算を繰り返さgenericLength なければならないことを意味します。

コードには1 つの関数呼び出しが含まれていますが、このルールを導入しないと2 つの隠し関数呼び出しが生成される可能性があり、直感に反します。

単型性制限により、 の型は次のようにfなります。

f :: Num a => [b] -> (a, a)

この方法では、計算を複数回実行する必要はありません。

  • ルール 1 はあいまいさを防ぎます。たとえば、宣言グループを考えてみましょう

     [(n,s)] = reads t
    

    readsこれは、シグネチャによって型が指定される標準関数であることを思い出してください。

     reads :: (Read a) => String -> [(a,String)]
    

    ルール 1 がなければ、 typeと typenが割り当てられます。後者は本質的にあいまいであるため、無効な型です。どのオーバーロードで を使用するかを決定することはできません。また、 の型シグネチャを追加することによってこれを解決することもできません。したがって、非単純なパターン バインディングが使用される場合 (セクション 4.4.3.2 )、推論される型は、型シグネチャが提供されるかどうかに関係なく、制約された型変数で常に単相です。この場合、 と の両方がで単形性です。∀ a. Read a ⇒ as∀ a. Read a ⇒ Stringssnsa

まあ、この例は自明だと思います。ルールを適用しないと型があいまいになる場合があります。

上記のように拡張機能を無効にすると、上記の宣言をコンパイルしようとすると型エラーが発生しますただし、これは実際には問題ではありません。使用するときにread、解析しようとする型をコンパイラに何らかの方法で伝える必要があることは既にわかっています...

第二のルール

  1. モジュール全体の型推論が完了したときに残っているモノモーフィックな型変数は、あいまいであると見なされ、デフォルト規則 (セクション 4.3.4 ) を使用して特定の型に解決されます。

この意味は。通常の定義がある場合:

plus = (+)

これは、上記の規則 1 により、 単相Num a => a -> a -> a型変数である型を持ちます。モジュール全体が推論されると、コンパイラは 、デフォルトの規則に従って、それを置き換える型を単に選択します。aa

最終結果は次のとおりplus :: Integer -> Integer -> Integerです。

これは、モジュール全体が推論された後に行われることに注意してください。

これは、次の宣言がある場合を意味します。

plus = (+)

x = plus 1.0 2.0

モジュール内では、型をデフォルトにする前にplus、型は次のようになります: Fractional a => a -> a -> a(これが起こる理由については、ルール 1 を参照してください)。この時点で、デフォルトのルールに従って、 andaに置き換えられるDouble ため、 and にplus :: Double -> Double -> Doubleなりx :: Doubleます。

不履行

前に述べたように、レポートのセクション 4.3.4 で説明されているいくつかのデフォルトルールが存在します。これは、推論器が採用でき、ポリモーフィック型をモノモーフィック型に置き換えるものです。これは、型があいまいな場合に発生します。

たとえば、式では次のようになります。

let x = read "<something>" in show x

showここでは、 andの型が次のreadとおりであるため、式があいまいです。

show :: Show a => a -> String
read :: Read a => String -> a

したがって、xhas type Read a => a. しかし、この制約は多くのタイプで満たされています: IntDoubleまたは()。どちらを選ぶ?私たちに言えることは何もありません。

この場合、必要な型をコンパイラに伝え、型シグネチャを追加することで、あいまいさを解決できます。

let x = read "<something>" :: Int in show x

問題は、Haskell がNum型クラスを使用して数値を処理するため、数値式にあいまいさが含まれる場合が多いことです。

検討:

show 1

結果はどうあるべきですか?

前と同じように1Num a => aがあり、使用できる数の型がたくさんあります。どちらを選ぶ?

数値を使用するたびにコンパイラ エラーが発生するのは良くないため、デフォルトのルールが導入されました。defaultルールは、宣言を使用して制御できます。指定するdefault (T1, T2, T3)ことで、推論器がさまざまな型をデフォルトにする方法を変更できます。

次の場合、あいまいな型変数vはデフォルト可能です。

  • vは、クラスであるという種類の制約でのみC v表示Cされます (つまり、: のように表示される場合は、デフォルト可能Monad (m v)ではありません)。
  • これらのクラスの少なくとも 1 つが であるNumか、そのサブクラスですNum
  • これらのクラスはすべて Prelude または標準ライブラリで定義されています。

デフォルト可能な型変数は、あいまいな変数のすべてのクラスのインスタンスであるリストの最初の型に置き換えられます。default

デフォルトのdefault宣言はdefault (Integer, Double).

例えば:

plus = (+)
minus = (-)

x = plus 1.0 1
y = minus 2 1

推論される型は次のとおりです。

plus :: Fractional a => a -> a -> a
minus :: Num a => a -> a -> a

これは、ルールをデフォルトにすることにより、次のようになります。

plus :: Double -> Double -> Double
minus :: Integer -> Integer -> Integer

sort これは、質問の例で定義のみがエラーを発生させる理由を説明していることに注意してください。は数値クラスではないため、型Ord a => [a] -> [a]をデフォルト設定できません。Ord

拡張デフォルト

GHCi には拡張されたデフォルト規則( GHC8 の場合はこちら) が付属していることに注意してください。これは、拡張機能を使用してファイル内で有効にすることもできますExtendedDefaultRules

デフォルト可能な型変数は、すべてのクラスが標準であり、 、、またはそのサブクラスの中に少なくとも 1 つのクラスがなければならない制約に現れる 必要があるだけではありません。EqOrdShowNum

さらに、デフォルトのdefault宣言はdefault ((), Integer, Double).

これにより、奇妙な結果が生じる場合があります。質問から例を挙げます:

Prelude> :set -XMonomorphismRestriction
Prelude> import Data.List(sortBy)
Prelude Data.List> let sort = sortBy compare
Prelude Data.List> :t sort
sort :: [()] -> [()]

ghci では、型エラーは発生しませんが、Ord a制約によってデフォルトが発生し、ほとんど役に立ちません()

便利なリンク

単型性の制限については、多くのリソースと議論があります。

以下は、私が役に立つと思ういくつかのリンクであり、トピックを理解したり、深く掘り下げたりするのに役立ちます。

于 2015-09-10T08:31:55.323 に答える