web-dev-qa-db-ja.com

静的に型付けされた言語はどのようにダックタイピングをサポートできますか?

動的および静的型システムとは何か、アヒル型付けとは何かを理解しています。しかし、ダックタイピングをサポートする静的言語をどのように持つことができるかわかりません。私の理解では、動的に型付けされた言語だけがダック型付けをサポートできます。

この回答 はStackOverflowから説明されています「ダックタイピングは、静的、動的、弱い、または強いタイピングと完全に直交するものです。」静的に型付けされた言語でのアヒル型付けのC++の例を示していますが、私はC++プログラマではないため、理解できません。

静的言語でダックタイピングをサポートする方法を説明してください。

6
Aviv Cohn

私の経験では、これは単に Structural Type System で静的型付けを使用する言語です。本質的には、「アヒルのように歩く、アヒルのように話す」チェックをコンパイルタイプに適用するため、プログラマは特にサブタイプのものに注釈を付ける必要がありません。

これは、2つ(またはそれ以上)のライブラリを一緒に接着しようとする場合に非常に大きなメリットがあります。名詞型システムでは、1つのライブラリにいくつかのインターフェイスがあり、別のライブラリにオブジェクトがある場合、それらはお互いを認識せず、サブタイプできません-オブジェクトであってもインターフェースの要件を満たします。構造タイピングはそれを大丈夫にします。

言語を静的にすることは、コンパイラがコンパイル時にそのチェックを行うことを意味し、そのオブジェクトが実際にはでないときにエラーを早期に(そして明確に)与えますインターフェースを満たす。

これを行う難解な言語はいくつかありますが、ほとんどの構造的に型付けされた言語も動的に型付けされ、ほとんどすべての主格型の言語も静的に型付けされます。

12
Telastyn

そのような用語の通常の意味は、単なる構造タイピングです。

構造的に型付けされたシステムでは、物事には静的な型があります。ただし、そのタイプは、特定のタイプ名ではなく、タイプの実際の構造に基づいています。

たとえば、Python code

def foo(bar):
  bar.baz()
  bar.quux(5)

barのタイプは明確ではありませんが、私たちが知っているものは何でも

  • 引数を取らないbazメソッドがあります
  • 整数引数を1つ取るメソッドquuxがあります

構造型システムでは、fooに型を割り当てることができます

foo : forall a b. r{baz : () -> a, quux : (int) -> b} -> Void

面白いrの意味するところ

メソッドrすべてのタイプ...

多くの言語は、構造的に型付けされた機能のサブセットを実装しています。たとえば、C++は、テンプレートを介して「構造的な型付け」を実装しています。ただし、これは少しアドホックなアプローチです。

他の言語は行タイプを実装します。これらは構造的に型付けされたレコード/構造体です! 「少なくともフィールドを持つレコードが欲しい...」のように言うことができるタイプ。 purescriptはこれらを実装していると思います。

Goには、「暗黙的なインターフェース」を持つ構造型のようなものがあります。これらは、型が自動的に実装する単なるインターフェースです。ただし、構造タイプをパラメトリックに処理することができないため、これは完全な構造タイプではありません。つまり、

foo :: r{a : int} -> w{a : int} -> r
foo r w = w -- Type error!!

すべてが単に不透明に扱われるのではなく、インターフェースに「アップキャスト」されるためです。

Haskellビューにこれらを追加する話がいくつかありました-XOverloadedRecordFields、しかし私はそれが完全な一般性である構造型付けの実際の進歩に気づいていません。

5
Daniel Gratzer

静的型付けとは、コンパイル時に型がチェックされることを意味します。型が特定の名前とシグネチャを持つメソッドを持っていることをコンパイラがチェックするのは、型が特定の継承階層の一部であることをチェックするのと同じくらい簡単です。トリックは、「この関数へのこの引数は、この特定の名前を持つメソッドを持つ任意の型です」を指定する簡潔な方法を見つけることです。

これを実現するために私が最もよく知っている方法は type class を使用することです。これは基本的に「これらのすべての関数を実装するすべての型はこの名前を使用して参照できる」という宣言です。

通常は、型を型クラスのインスタンスとして具体的に宣言する必要がありますが、型自体が定義されているのと同じコード内にある必要はありません。つまり、誰でも自分の型クラスを型に追加できます。制御しないでください。これは、「アヒルのように歩き、アヒルのように鳴る」と、アヒルの完全なタイピングに対する一種の妥協です。たとえば、だれでもアヒルのようなものを誰でも明示的に宣言でき、他の誰もがアヒルのように扱うことができます。」

ただし、型を型クラスに暗黙的に追加できるようにするのはそれほど大きくはありません。少し制御を失うだけです。 Amon および Jozefg が指摘したように、go言語には、基本的に暗黙的に追加された型クラスのように動作するインターフェイスがあります。

4
Karl Bielefeldt

ご存知ですか TypeScript

単純化した例のように思われるかもしれませんが、ここで説明します。それは、Javascriptにコンパイルされる言語です。これで、JavaScript自体は動的にアヒル型になりましたが、少しの間、JSについては忘れましょう。

TypeScriptは静的型付けをサポートしています。つまり、コンパイル中に型が正しいかどうかをチェックし、エラーが発生したと思われる場合は警告を出力します。 (あなたは tutorial をフォローアップすることができ、これはあなたにこの例を示します)

ただし、静的型付けをサポートしている場合でも、JSダックの型付け全体を備えています。 TypeScriptが追加する型を省略すれば、どこでもvarを使用でき、コードがコンパイルおよび実行されます。

つまり、静的に型付けされますが、いんちきすることもできます。あなたがそれをさせた場合、それはあなたのためにいんちきをチェックさえします。

アヒルのタイピングは、タイピングの利便性のため、また一部の言語ではジェネリック(C++など)を使用するためのものです。静的型付けは、コンパイル済み言語で作業している場合に、コンパイル中に指定した型をチェックするためのものです。

2
ArthurChamz

である必要はありません。それほど多くの作業をせずに、静的言語でダックタイピングの95%を実装できます。依存型入力が必要な最後の5%ははるかに複雑ですが、それでも静的型付けシステムです。具体的には、残りの5%は、他の値に応じてメソッドを条件付きで使用するためのものであり、条件付きで呼び出すすべてのメソッドをサポートしていないデータで機能するコードを使用します。

これが私がいじくっていたHaskellのチャンクであり、それは何かを非常に多く実装していますlike静的ダックタイピング:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, 
UndecidableInstances, IncoherentInstances, FlexibleContexts #-}

data Addon a b = Addon a b deriving Show

class Plugin a b where
    first :: b -> a
    set   :: a -> b -> b

instance Plugin a a where
    first = id
    set   = const

instance Plugin a (Addon a b) where
    first (Addon a _) = a
    set x (Addon a b) = Addon x b

instance (Plugin a b) => Plugin a (Addon c b)where
    first (Addon _ b) = first b
    set x (Addon a b) = Addon a $ set x b

class Runnable e b c where
    run :: e -> b -> c

instance Plugin a b => Runnable (a -> c) b c where
    run f p = f (first p)

instance (Plugin d b, Runnable e b c) => Runnable (d -> e) b c where
    run f p = run (f $ first p) p

-- The constructor function should NOT be polymorphic *at all*
apply constructor f x = set (constructor $ run f x) x

プラグインの型クラスは、他の型内の型の包含を表現するために存在します。 Plugin a bのインスタンスであるタイプは、first bがタイプbの値になることができるタイプaであることを意味します。指定された2つのインスタンスは、関連するすべてのケースを導出するために必要なすべてであり、それ以上のインスタンス宣言は必要ありません。

Runnableタイプクラスを使用すると、プラグインインスタンスをチェーン化できるため、関数のカリー化された引数ごとに有効なコンポーネントタイプが含まれている限り、単一のデータ型に対してカリー化された複数引数関数を呼び出すことができます。繰り返しになりますが、使用のためにインスタンスをさらに宣言する必要はありません。

Apply関数を使用すると、複合型を取得して関数を適用し、結果をその複合型内に格納できます(格納する型の値がすでに含まれている場合)。

使用法は次のようになります。

data Increment = Inc Integer
data Counter   = Count Integer

counter1 = union (Inc 2) (Count 3)
counter2 = union (Inc 1) (Count 0)

increment (Inc i) (Count c) = i + c

そして実際、これはコンパイルされ、実行して出力を観察できます。

*Main> apply Count increment counter1
Addon (Inc 2) (Count 5)
*Main> apply Count increment counter2
Addon (Inc 1) (Count 1)

確かに、かなり醜い型の拡張機能を有効にする必要がありましたが、それらの拡張機能はライブラリを使用するために必要ではなく、ライブラリを作成するためだけに必要です。適用するコンストラクター引数は、型推論システムをオーバーロードし、必要な中間型の派生を行うことができないため、多態性にすることはできません。私はそれを回避しようとしていましたが、その方向に進んでいません。

0