23

これはおそらく非常に基本的な質問ですが...

次のように定義された関数

foo :: a -> Integer

任意の型から整数への関数を示します。もしそうなら、理論的には、そのように任意のタイプに対してそれを定義できるはずです

foo 1 = 10
foo 5.3 = 100
foo (x:xs) = -1
foo  _     = 0

しかし、Haskell では、 のような一般的な定義しか許可されていませんfoo a = 0

aまた、 Show typeclass のインスタンスなど、型の特定のクラスの 1 つに制限したとしても、次のようになります。

foo :: (Show a) => a -> Integer

あなたはまだ何かをすることはできません

foo "hello" = 10
foo   _     = 0

"hello" :: [Char]のインスタンスですがShow

なぜそのような制限があるのですか?

4

5 に答える 5

32

これは機能であり、実際には非常に基本的です。それは、プログラミング言語理論のパラメトリシティとして知られている特性に要約されます。大まかに言うと、これは、評価がコンパイル時に変数である型に依存してはならないことを意味します。具体的なタイプが静的にわからない値を見ることはできません。

なぜそれが良いのですか?それはプログラムに関してはるかに強い不変条件を与えます。たとえばa -> a、恒等関数(または分岐)でなければならないタイプだけからわかります。同様の「自由定理」は、他の多くの多形関数に適用されます。パラメトリシティは、より高度なタイプベースの抽象化手法の基礎でもあります。たとえば、ST s aHaskell(状態モナド)の型と対応する関数の型は、パラメトリックであることrunSTに依存しています。sこれにより、実行中の関数が状態の内部表現を混乱させることがなくなります。

効率的な実装のためにも重要です。プログラムは実行時にコストのかかる型情報を渡す必要がなく(型消去)、コンパイラーは異なる型に対して重複する表現を選択できます。後者の例として、0とFalse、および()と[]は、実行時にすべて0で表されます。あなたのような機能が許可されていれば、これは不可能です。

于 2012-05-30T09:52:27.637 に答える
21

Haskell は、「型消去」として知られる実装戦略を採用しています。型には計算上の重要性がないため、出力するコードは型を追跡する必要がありません。これは、パフォーマンスにとって大きなメリットです。

このパフォーマンス上の利点に対して支払う代償は、型が計算上の重要性を持たないことです。関数は、渡された引数の型に基づいて動作を変更できません。次のようなものを許可する場合

f () = "foo"
f [] = "bar"

その場合、そのプロパティは真ではありませんf。実際、の動作は最初の引数の型に依存します。

この種のことを可能にする言語は確かにあります。特に、型を一般的に消去できない依存型付き言語ではそうです。

于 2012-05-30T07:36:45.200 に答える
20

関数a -> Integerの場合、許可される動作は 1 つだけです。つまり、定数の整数を返します。なんで?タイプがわからないからですa。制約が指定されていない場合、それはまったく何でもありえます。また、Haskell は静的に型付けされているため、コンパイル時に型に関するすべてを解決する必要があります。実行時には、型情報は存在しないため参照できません。使用する関数の選択はすべて既に行われています。

Haskell がこの種の動作に最も近いのは、型クラスの使用です - Foo1 つの関数で呼び出される型クラスを作成した場合:

class Foo a where
    foo :: a -> Integer

次に、さまざまなタイプのインスタンスを定義できます

instance Foo [a] where
    foo [] = 0
    foo (x:xs) = 1 + foo xs

instance Foo Float where
    foo 5.2 = 10
    foo _ = 100

x次に、いくつかのパラメーターが a であることを保証できる限り、それFooを呼び出すことができますfoo。ただし、それでも必要です-関数を作成することはできません

bar :: a -> Integer
bar x = 1 + foo x

aコンパイラはそれが のインスタンスであることを認識していないためですFoo。それを伝えるか、型シグネチャを省略して、それを自分で理解させる必要があります。

bar :: Foo a => a -> Integer
bar x = 1 + foo x

Haskell は、何かの型についてコンパイラーが持っている情報だけでしか動作できません。これは制限的に聞こえるかもしれませんが、実際には型クラスとパラメトリック ポリモーフィズムは非常に強力であるため、動的型付けを見逃すことはありません。実際、私は通常、動的型付けが面倒だと感じています。

于 2012-05-30T08:05:16.980 に答える
16

あなたがそれを説明しているように、タイプは実際には「任意のタイプから」へ機能a -> Integerを意味するものではありません。定義または式にタイプがある場合、それは、任意のタイプについて、この定義または式をタイプの関数に特殊化またはインスタンス化できることを意味します。Integera -> IntegerTT -> Integer

表記を少し切り替えると、これを考える1つの方法は、foo :: forall a. a -> Integer実際には2つの引数の関数であるということです。1つの型aとその型の値ですa。または、カリー化に関してfoo :: forall a. a -> Integerは、引数として型を取り、Tそのための型の特殊な関数を生成する関数T -> IntegerですT。例として恒等関数(結果として引数を生成する関数)を使用すると、次のようにこれを示すことができます。

-- | The polymorphic identity function (not valid Haskell!)
id :: forall a. a -> a
id = \t -> \(x :: t) -> x

ポリモーフィズム関数の型引数としてポリモーフィズムを実装するというこのアイデアは、Haskellが実際に理論的なソースの1つとして使用しているSystemF呼ばれる数学的フレームワークに由来しています。ただし、Haskellは型パラメーターを関数への引数として渡すという考えを完全に隠しています。

于 2012-05-30T18:52:47.507 に答える
12

この質問は間違った前提に基づいています.Haskellはそれを行うことができます. (ただし、通常は非常に特定の状況でのみ使用されます)

{-# LANGUAGE ScopedTypeVariables, NoMonomorphismRestriction #-}

import Data.Generics

q1 :: Typeable a => a -> Int
q1 = mkQ 0 (\s -> if s == "aString" then 100 else 0)

q2 :: Typeable a => a -> Int
q2 = extQ q1 (\(f :: Float) -> round f)

これをロードして試してみてください:

Prelude Data.Generics> q2 "foo"
0
Prelude Data.Generics> q2 "aString"
100
Prelude Data.Generics> q2 (10.1 :: Float)
10

これは、クレームの種類に計算上の意味がないという回答と必ずしも矛盾しません。これは、これらの例では、Typeable実行時にアクセス可能なデータ値に型を具体化する制約が必要だからです。

いわゆるジェネリック関数 (SYB など) のほとんどは、TypeableまたはData制約に依存しています。一部のパッケージは、本質的に同じ目的を果たす独自の代替機能を導入しています。これらのクラスのようなものがなければ、これを行うことはできません。

于 2012-05-30T09:55:25.270 に答える