19

私が Haskell について読んだことと、GHC で行った実験に基づくと、Haskell には戻り値の型のオーバーロード (アドホック ポリモーフィズム) があるようです。この一例は、結果が使用される場所に応じてまたはfromIntegerを与えることができる関数です。例えば:DoubleInteger

fd :: Double -> String
fd x = "Double"

fi :: Integer -> String
fi x = "Integer"

fd (fromInteger 5)  -- returns "Double"
fi (fromInteger 5)  -- returns "Integer"

Haskell への穏やかな紹介は、次のように述べている場合、これに同意しているようです。

これまで説明してきた種類のポリモーフィズムは、一般にパラメトリック ポリモーフィズムと呼ばれます。オーバーロードとしてよく知られている、アドホック ポリモーフィズムと呼ばれる別の種類があります。アドホック ポリモーフィズムの例を次に示します。

  • リテラル 1、2 などは、固定精度整数と任意精度整数の両方を表すためによく使用されます。

数値リテラルがアドホック ポリモーフィズム (別名オーバーロード) の例であると見なされる場合、 のような関数の結果についても同じことが当てはまるようfromIntegerです。

実際、Haskell が戻り値の型によるオーバーロードを行っていることを示唆する、スタック オーバーフローに関する他の質問に対する回答をいくつか見つけました。

ただし、少なくとも 1 人の Haskell プログラマーは、これは戻り値の型のオーバーロードではなく、「パラメーターが全称量指定子によってバインドされるパラメトリック ポリモーフィズム」の例であると私に言いました。

彼が得ているのは、 (非決定論的なタイプの)すべてのインスタンスから値を返すことだと思いますfromIntegerNum

これは妥当な解釈のように思えますが、私が知る限り、Haskell ではこれらのインスタンス値を複数見ることはできません (モノモーフィズムの制限のおかげもあります)。また、私たちが見ている値の実際のインスタンスは静的に決定できるようです。fd (fromInteger 5)このすべてのことから、式の部分式は 型であるのに対し、式の部分式fromInteger 5は 型Doublefi (fromInteger 5)あるfromInteger 5と言うのが妥当であるように思われIntegerます。

では、Haskell には戻り値の型のオーバーロードがありますか?

そうでない場合は、次のいずれかの例を提供してください。

  • Haskell に戻り値の型のオーバーロードがある場合、異なる動作をする有効な Haskell コード
  • Haskell に戻り値の型のオーバーロードがある場合は無効になる有効な Haskell コード
  • Haskell に戻り値の型のオーバーロードがある場合に有効な無効な Haskell コード
4

5 に答える 5

17

それを見る 1 つの方法は、Haskell が、型クラスの辞書渡し変換と呼ばれるものを使用して、あなたが考えている戻り値の型ポリモーフィズムをパラメトリック ポリモーフィズムに変換することです。(ただし、型クラスを実装する方法や型クラスについて推論する方法はこれだけではありません。最も一般的な方法です。)

基本的に、fromIntegerHaskell には次の型があります。

fromInteger :: Num a => Integer -> a

内部的には、次のように変換できます。

fromInteger# :: NumDictionary# a -> Integer -> a
fromInteger# NumDictionary# { fromInteger = method } x = method x

data NumDictionary# a = NumDictionary# { ...
                                       , fromInteger :: Integer -> a
                                       , ... }

したがって、インスタンスTを持つ具象型ごとに、 function を含む値があり、型クラスを使用するすべてのコードは、ディクショナリを引数として受け取るコードに変換されます。NumNumDictionary# TfromInteger :: Integer -> TNum

于 2012-07-10T19:14:26.140 に答える
13

Haskell スタイルの型クラスに関する重要な論文は、「アドホック ポリモーフィズムをアドホックでなくする方法」と呼ばれています。したがって、あなたの質問への答えは「はい」です - 戻り値の型のオーバーロードをどの程度アドホックにするかによって異なります...

言い換えれば、アドホック ポリモーフィズムが型クラスに関連することに疑いの余地はありません。しかし、結果が依然として「戻り値の型のオーバーロード」とみなされると考えるかどうかは、好みの定義の面倒な詳細に依存します。

于 2012-07-10T19:23:14.640 に答える
12

私はあなたの質問の1つの小さな部分に対処したいと思います:

また、私たちが見ている実際のインスタンスは静的に決定できるようです。

これは実際には正確ではありません。次の奇抜なデータ型を考えてみましょう。

data PerfectlyBalancedTree a
    = Leaf a
    | Branch (PerfectlyBalancedTree (a,a))
    deriving (Eq, Ord, Show, Read)

良い部分に移る前に、まずそのタイプを少し見てみましょう。タイプのいくつかの典型的な値は次のPerfectlyBalancedTree Integerとおりです。

Leaf 0
Branch (Leaf (0, 0))
Branch (Branch (Leaf ((0,0),(0,0))))
Branch (Branch (Branch (Leaf (((0,0),(0,0)),((0,0),(0,0))))))

Branch実際、このタイプの任意の値は、n個のタグの最初のシーケンスであり、その後に「最終的に完了しました。よろしくお願いします」Leafタグが続き、含まれているタイプの2^nタプルが続くものとして視覚化できます。涼しい。

次に、これらの表現をもう少し便利に解析する関数を記述します。呼び出しの例を次に示します。

*Main> :t fromString
fromString :: String -> PerfectlyBalancedTree Integer
*Main> fromString "0"
Leaf 0
*Main> fromString "b(42,69)"
Branch (Leaf (42,69))
*Main> fromString "bbb(((0,0),(0,0)),((0,0),(0,0)))"
Branch (Branch (Branch (Leaf (((0,0),(0,0)),((0,0),(0,0))))))

途中で、もう少しポリモーフィックな関数を書くと便利です。ここにあります:

fromString' :: Read a => String -> PerfectlyBalancedTree a
fromString' ('b':rest) = Branch (fromString' rest)
fromString' leaf = Leaf (read leaf)

これで、実際の関数は同じものになり、型アノテーションが異なります。

fromString :: String -> PerfectlyBalancedTree Integer
fromString = fromString'

しかし、ちょっと待ってください...ここで何が起こったのですか?私はあなたのそばで何かを滑らせました!なぜこれを直接書かなかったのですか?

fromStringNoGood :: String -> PerfectlyBalancedTree Integer
fromStringNoGood ('b':rest) = Branch (fromStringNoGood rest)
fromStringNoGood leaf = Leaf (read leaf)

その理由は、再帰呼び出しでfromStringNoGoodは、が異なるタイプであるためです。を返すように求められているのではなく、を返すようPerfectlyBalancedTree Integerに求められていPerfectlyBalancedTree (Integer, Integer)ます。そのような関数を自分で書くことができます...

fromStringStillNoGood :: String -> PerfectlyBalancedTree Integer
fromStringStillNoGood ('b':rest) = Branch (helper rest)
fromStringStillNoGood leaf = Leaf (read leaf)

helper :: String -> PerfectlyBalancedTree (Integer, Integer)
helper ('b':rest) = Branch ({- ... what goes here, now? -})
helper leaf = Leaf (read leaf)

...しかし、この方法は、より深く、より深くネストされた型を書くことへの無限後退にあります。

問題は、モノモーフィックなトップレベル関数に関心があるとしても、それが使用するポリモーフィック関数でどのタイプが呼び出されているかを静的に判別できないことです。渡されたデータによって、返されるreadタプルのタイプが決まります。sが多いほど、ネストされたタプルが深くなります。readbString

于 2012-07-10T21:32:10.990 に答える
5

そうです:Haskellにはオーバーロードがあり、型クラスメカニズムを通じてそれを提供します。

次の署名を検討してください。

f :: [a] -> a
g :: Num a => [a] -> a

最初のシグニチャは、任意のタイプの要素のリストが与えられると、aタイプfの値を生成することを示していますa。これは、の実装では、タイプとそれが許可する操作fについて何も想定できないことを意味します。aこれは、パラメトリック多型の例です。少し振り返ってみると、実際には実装するためのオプションはほとんどありfません。実行できるのは、提供されたリストから要素を選択することだけです。f概念的には、すべてのタイプで機能する単一のジェネリック実装がありますa

2番目のシグニチャは、型クラスにa属するある型の要素のリストが与えられると、その型の値を生成することを示しています。これは、の実装が型クラスに付属するすべての操作を使用して型の値を消費、生成、および操作できることを意味します。たとえば、リストの要素を追加または乗算したり、リストの最小値を選択したり、持ち上げられた定数を返したりすることができます...これはオーバーロードの例であり、通常はアドホック多相性の形式と見なされます(他の主な形式は強制です)。概念的には、のすべてのタイプに対して異なる実装があります。NumgagaNumggaNum

于 2012-07-10T19:20:04.030 に答える
1

戻り型のオーバーロードがあります。良い例として、Read 関数を参照してください。タイプは Read a => String -> a です。読み取り型クラスを実装するものはすべて読み取って返すことができます。

于 2012-07-10T19:30:04.187 に答える