関数の戻り値の交差タイプをサポートする静的型付け言語はどれですか?


17

最初のメモ:

私が探していたものを正確に述べるための適切な用語が欠けていたため、この質問は数回の編集の後に閉じられました。その後、Sam Tobin-Hochstadtがコメントを投稿しました。これは、関数の戻り値の交差タイプをサポートするプログラミング言語を正確に認識させるものです。

質問が再び開かれたので、(できれば)より正確な方法で書き直すことで改善することにしました。したがって、以下の回答とコメントは以前の編集を参照しているため、意味をなさない場合があります。(そのような場合は質問の編集履歴をご覧ください。)

関数の戻り値の交差タイプをサポートする、静的で強く型付けされた一般的なプログラミング言語(Haskell、汎用Java、C#、F#など)はありますか?もしそうなら、どれ、そしてどのように?

(正直なところ、C#やJavaなどの主流言語で交差タイプを表現する方法を誰かがデモンストレーションするのを見てみたいです。)

C#に似た疑似コードを使用して、交差タイプがどのように見えるかの簡単な例を示します。

interface IX { … }
interface IY { … }
interface IB { … }

class A : IX, IY { … }
class B : IX, IY, IB { … }

T fn()  where T : IX, IY
{
    return … ? new A()  
             : new B();
}

つまり、関数fnはあるタイプのインスタンスを返します。T呼び出し側は、それがインターフェースIXとを実装していることだけを知っていますIY。(つまり、ジェネリックとは異なり、呼び出し側は具体的な型を選択することはできませんT—関数はそうします。これからT、実際にはユニバーサル型ではなく、実存型であると思います。)

PS:を単に定義しinterface IXY : IX, IY、の戻り値の型をfnに変更できることを知っていますIXY。しかし、それは、実際には同じものではありませんので、多くの場合、あなたの追加インターフェイスのないボルトができIXY、以前に定義された型にAのみ実装IXし、IY個別に。


脚注:交差点の種類に関するリソース:

「タイプシステム」のウィキペディアの記事には、交差タイプに関するサブセクションがあります

ベンジャミンC.ピアース(1991)のレポート、「交差点タイプ、ユニオンタイプ、およびポリモーフィズムを使用したプログラミング」

David P. Cunningham(2005)、「Intersection types in practice」、ウィキペディアの記事で言及されているForsythe言語に関する事例研究が含まれています。

スタックオーバーフローの質問、「ユニオンの種類と交差点の種類」にはいくつかの良い答えがありましたが、この質問では、上記のものと同様の交差点の種類の擬似コード例を示しています。


6
これはどのようにあいまいですか?Tそれは単に「/実装拡張し、いくつかのタイプとして、関数宣言内で定義されていた場合でも、型を定義IXしてのIY」。事実、実際の戻り値はその特殊なケースである(AまたはBそれぞれ)は、あなただけのようにも使用してそれを実現でき、ここでは何も特別なものではありませんObject代わりにT
ヨアヒムザウアー

1
Rubyでは、関数から必要なものを返すことができます。他の動的言語についても同様です。
トーステンミュラー

回答を更新しました。@Joachim:「曖昧な」という用語は問題の概念を非常に正確に捉えていないことを知っているので、例は意図した意味を明確にするものです。
stakx

1
広告PS:...質問を「TインターフェイスのIすべてのメソッドを実装するときにインターフェイスとして型を扱うことを許可するが、そのインターフェイスを宣言しなかった言語」に変更します。
ジャン・ヒューデック

6
この質問を閉じるのは間違いでした。なぜなら、正確な答えがあり、それはユニオンのタイプだからです。ユニオンタイプは、(Typed Racket)[ docs.racket-lang.org/ts-guide/]などの言語で利用できます。
サムトビンホッホシュタット

回答:


5

Scalaには、言語に組み込まれた完全な交差タイプがあります。

trait IX {...}
trait IY {...}
trait IB {...}

class A() extends IX with IY {...}

class B() extends IX with IY with IB {...}

def fn(): IX with IY = if (...) new A() else new B()

dottyベースのスカラには、真の交差タイプがありますが、現在のスカラにはありません。
Hongxu陳

9

実際、明白な答えは次のとおりです。Java

Javaが交差タイプをサポートしていることを知ると驚くかもしれませんが、実際には「&」タイプバウンド演算子を使用します。例えば:

<T extends IX & IY> T f() { ... }

参照してください。このリンクを Javaで複数の種類の境界に、そしてまた、この JavaのAPIから。


コンパイル時に型がわからない場合でも動作しますか?すなわち1つ書くことができます<T extends IX & IY> T f() { if(condition) return new A(); else return new B(); }。そして、そのような場合にどのように関数を呼び出すのですか?どちらを取得するかわからないため、AもBも通話サイトに表示できません。
ジャン・ヒューデック

はい、あなたは正しいです---具体的な型を提供する必要があるので、与えられた元の例と同等ではありません。交差点の境界でワイルドカードを使用できれば、それができます。私たちにはできないようです...そして、なぜそうなのか本当に分かりません(これを参照)。しかし、Javaには、ある種の交差タイプがあります
...-redjamjar

1
Javaをやった10年間で交差点の種類を何とか学んだことがないことに失望します。Flowtypeを常に使用するようになったので、それらはJavaの最大の欠けている機能だと思いましたが、実際に一度も見たことがないというだけです。人々は真剣にそれらを十分に活用していません。よく知られていれば、Springのような依存性注入フレームワークはそれほど人気が​​なかったでしょう。
アンディ

8

元の質問は「あいまいなタイプ」を求めました。そのための答えは:

あいまいなタイプ、明らかになし。呼び出し元は、何を取得するかを知る必要があるため、それは不可能です。返すことができるすべての言語は、基本型、インターフェイス(交差型のように自動生成される場合があります)、または動的型(動的型は基本的に、名前による呼び出し、get、およびsetメソッドを持つ型です)のいずれかです。

推定インターフェース:

だから、基本的にはあなたはそれがインターフェイスを返すようにしたいIXY導出していることIX と、 IYそのインターフェイスは、いずれかで宣言されていなかったけれどもAまたはB、それらの型が定義された時に宣言されていない可能性があるため。その場合:

  • 明らかに動的に型付けされるもの。
  • 私は静的型付けの主流言語がインターフェースを生成することができるだろう覚えていません(それは組合の種類AおよびBまたは交差点の種類IXIY自身)。
  • GO。これは、クラスが正しいメソッドを持っている場合、それらを宣言せずにインターフェースを実装するためです。したがって、2つを派生させるインターフェイスを宣言して、それを返すだけです。
  • 明らかに、その型の定義以外のインターフェイスを実装するために型を定義できる他の言語ですが、GO以外は覚えていないと思います。
  • 型定義自体でインターフェースの実装を定義する必要がある型では不可能です。ただし、2つのインターフェイスを実装し、すべてのメソッドをラップされたオブジェクトに委任するラッパーを定義することにより、それらのほとんどで回避できます。

PS A 、強く型付けされた言語はながら、所与のタイプのオブジェクトが、別の型のオブジェクトとして扱うことができないものである、弱く型付けされた言語は、再解釈キャストを有するものです。したがって、動的に型付けされる言語はすべて強く型付けされ、弱い型付け言語はアセンブリ、CおよびC ++であり、3つすべてが静的に型付けされます。


これは正しくありませんが、そのタイプに曖昧さはありません。言語は、いわゆる「交差タイプ」を返すことができます---もし主流の言語がそうするのであれば、それはほんのわずかです。
redjamjar

@redjamjar:質問に答えて「曖昧なタイプ」を尋ねたとき、質問の表現が異なっていました。それがそれで始まる理由です。それ以降、質問は大幅に書き直されました。答えを拡張して、元の文言と現在の文言の両方に言及します。
ジャン・ヒューデック

すみません、明らかにそれを見逃しました!
redjamjar

Golangに言及するための+1。これはおそらく、それを行う方法が少し遠回りであっても、これを可能にする共通言語の最良の例です。
ジュール

3

Goプログラミング言語 はこれがありますが、これはインターフェイスタイプ専用です。

Goでは、正しいメソッドが定義されているすべてのタイプが自動的にインターフェイスを実装するため、PSの異議は適用されません。つまり、インターフェイスタイプのすべての操作を組み合わせたインターフェイスを作成するだけで(シンプルな構文があります)、すべて機能します。

例:

package intersection

type (
    // The first component type.
    A interface {
        foo() int
    }
    // The second component type.
    B interface {
        bar()
    }

    // The intersection type.
    Intersection interface {
        A
        B
    }
)

// Function accepting an intersection type
func frob(x Intersection) {
    // You can directly call methods defined by A or B on Intersection.
    x.foo()
    x.bar()

    // Conversions work too.
    var a A = x
    var b B = x
    a.foo()
    b.bar()
}

// Syntax for a function returning an intersection type:
// (using an inline type definition to be closer to your suggested syntax)
func frob2() interface { A; B } {
    // return something
}

3

ジェネリックおよびバインドされたポリモーフィズム(C#など)を備えた任意の言語でエンコードできるバインドされた実存型を使用して、必要な処理を実行できる場合があります。

戻り型は次のようなものになります(擬似コード内)

IAB = exists T. T where T : IA, IB

またはC#の場合:

interface IAB<IA, IB>
{
    R Apply<R>(IABFunc<R, IA, IB> f);
}

interface IABFunc<R, IA, IB>
{
    R Apply<T>(T t) where T : IA, IB;
}

class DefaultIAB<T, IA, IB> : IAB<IA, IB> where T : IA, IB 
{
    readonly T t;

    ...

    public R Apply<R>(IABFunc<R, IA, IB> f) {
        return f.Apply<T>(t);
    }
}

注:これはテストしていません。

点は、あるIAB任意の戻りタイプにIABFuncを適用することができなければならないR、そしてIABFunc任意に作業することができなければならないT両方のサブタイプIAIB

の目的はDefaultIAB、既存のTwhichサブタイプIAとをラップすることだけIBです。これは、後で既存のものにいつでも追加できるIAB : IA, IBという点で異なることに注意してください。DefaultIABT

参照:


アプローチは、パラメーターT、IA、IBを含む汎用オブジェクトラッパータイプを追加し、Tがインターフェイスに制限さApplyれている場合に機能します。大きな問題は、インターフェイスを実装するために匿名関数を使用する方法がないため、そのような構造は実際に使用するのが苦痛になることです。
supercat 14年

3

TypeScriptは、T & U(ユニオン型とともにT | U)交差型をサポートする別の型付き言語です。高度なタイプに関するドキュメントページから引用された例を次に示します

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

2

Ceylonは、ファーストクラスのユニオンおよび交差タイプを完全にサポートしています

ユニオンタイプをX | Y、交差タイプをとして記述しX & Yます。

さらに良いことに、セイロンは、これらのタイプに関する多くの洗練された推論を備えています。

  • 主要インスタンス化:例えば、Consumer<X>&Consumer<Y>同じタイプであるConsumer<X|Y>場合Consumerに反変されX、そして
  • ばらばら:たとえば、ボトムタイプObject&Nullと同じNothingタイプです。

0

C ++関数はすべて固定の戻り値型を持っていますが、ポインターを返す場合、ポインターは制限付きで異なる型を指すことができます。

例:

class Base {};
class Derived1: public Base {};
class Derived2: public Base{};

Base * function(int derived_type)
{
    if (derived_type == 1)
        return new Derived1;
    else
        return new Derived2;
}

返されるポインターの動作は、virtual定義されている関数によって異なります。たとえば、

Base * foo = function(...);dynamic_cast<Derived1>(foo)

これが、C ++での多態性の仕組みです。


そしてもちろん、boostが提供するテンプレートのように、anyまたはを使用できvariantます。したがって、制限は残りません。
デデュプリケーター

これは非常に疑問が戻り値の型が同時に2つの識別スーパークラスを拡張することを指定する方法のためだった、が、求めていたものを、すなわちされていませんclass Base1{}; class Base2{}; class Derived1 : public Base1, public Base2 {}; class Derived2 : public Base1, public Base2 {}...今何種類我々はそれを指定することができますいずれかを返すことができないDerived1か、Derived2どちらもBase1またBase2直接?
ジュール

-1

Python

非常に強く型付けされています。

ただし、関数の作成時には型は宣言されていないため、返されるオブジェクトは「あいまい」です。

あなたの特定の質問では、より良い用語は「多態性」かもしれません。Pythonの一般的な使用例は、共通のインターフェイスを実装するバリアント型を返すことです。

def some_function( selector, *args, **kw ):
    if selector == 'this':
        return This( *args, **kw )
    else:
        return That( *args, **kw )

Pythonは強く型付けされているため、結果のオブジェクトは、別のタイプのオブジェクトのインスタンスであるThisThat、または(簡単に)強制またはキャストできません。


これは非常に紛らわしいです。オブジェクトのタイプはほとんど不変ですが、値はタイプ間で非常に簡単に変換できます。strに、例えば、ささいに。
ジェームズヤングマン

1
@JamesYoungman:なに?それはすべての言語に当てはまります。私が今まで見たすべての言語では、to_string変換が左、右、中央にあります。あなたのコメントはまったく得られません。詳しく説明してもらえますか?
-S.Lott

「Pythonは強く型付けされている」という意味を理解しようとしていました。おそらく、「強く型付けされた」という意味を誤解したのでしょう。率直に言って、Pythonには、厳密に型指定された言語に関連付ける特性がほとんどありません。たとえば、関数の戻り値の型が呼び出し側の値の使用と互換性がないプログラムを受け入れます。たとえば、「x、y = F(z)」の場合、F()は(z、z、z)を返します。
ジェームズヤングマン

Pythonオブジェクトのタイプを変更することはできません(重大な魔法がなければ)。JavaやC ++にあるような「キャスト」演算子はありません。これにより、各オブジェクトが強く型付けされます。変数名と関数名には型バインディングはありませんが、オブジェクト自体は厳密に型指定されています。ここで重要な概念は、宣言の存在ではありません。重要な概念は、キャスト演算子の利用可能性です。また、これは事実であるように見えることに注意してください。ただし、モデレーターはこれに異議を唱える可能性があります。
S.Lott

1
CおよびC ++キャスト演算も、オペランドのタイプを変更しません。
ジェームズヤングマン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.