「型分離」(共用体型)を定義する方法は?


181

提案されいる 1つの方法オーバーロードされたメソッドの二重定義を処理するためにれ方法は、オーバーロードをパターンマッチングで置き換えることです。

object Bar {
   def foo(xs: Any*) = xs foreach { 
      case _:String => println("str")
      case _:Int => println("int")
      case _ => throw new UglyRuntimeException()
   }
}

このアプローチでは、への引数の静的型チェックを放棄する必要がありますfoo。書くことができればもっといいでしょう

object Bar {
   def foo(xs: (String or Int)*) = xs foreach {
      case _: String => println("str")
      case _: Int => println("int")
   }
}

私はと近づくことができますEitherが、2種類以上のタイプでは醜く速くなります:

type or[L,R] = Either[L,R]

implicit def l2Or[L,R](l: L): L or R = Left(l)
implicit def r2Or[L,R](r: R): L or R = Right(r)

object Bar {
   def foo(xs: (String or Int)*) = xs foreach {
      case Left(l) => println("str")
      case Right(r) => println("int")
   }
}

それは定義が必要となる一般的な(エレガント、効率的な)解決策のように見えEither3Either4同じ目的を達成するための代替ソリューションの、......ん誰のノウハウを?私の知る限り、Scalaには組み込みの「型分離」はありません。また、上記で定義された暗黙的な変換は、標準ライブラリのどこかに潜んでいるので、それらをインポートすることができますか?

回答:


142

まあ、特定のケースでは Any*、以下のこのトリックは機能しません。混合型を受け入れないためです。ただし、混合型はオーバーロードでも機能しないため、これが必要な場合があります。

最初に、以下のように、受け入れたい型を持つクラスを宣言します。

class StringOrInt[T]
object StringOrInt {
  implicit object IntWitness extends StringOrInt[Int]
  implicit object StringWitness extends StringOrInt[String]
}

次に、次のfooように宣言します。

object Bar {
  def foo[T: StringOrInt](x: T) = x match {
    case _: String => println("str")
    case _: Int => println("int")
  }
}

以上です。foo(5)またはを呼び出すとfoo("abc")、動作しますが、試してみるfoo(true)と失敗します。これはStringOrInt[Boolean]、以下のRandallで言及されているように、StringOrIntsealedクラスを。

これT: StringOrIntは、型の暗黙のパラメーターがあるStringOrInt[T]こと、およびScalaが型のコンパニオンオブジェクトを調べて、その型を要求するコードを機能させるための暗黙の型があるかどうかを確認するために機能します。


14
場合class StringOrInt[T]行われsealed、「漏れ」は、あなたが(「もちろん、これはサイドステップ作成することにより、クライアントコードで可能性に言及StringOrInt[Boolean]、接続されている」)少なくともあればStringOrInt、独自のファイルに存在します。次に、ウィットネスオブジェクトをと同じソースで定義する必要がありますStringOrInt
Randall Schulz、

3
私はこのソリューションを少し一般化してみました(以下の回答として投稿されています)。Eitherアプローチと比較した主な欠点は、一致をチェックするための多くのコンパイラサポートを失うことです。
Aaron Novstrup、2010

素敵なトリック!ただし、シールされたクラスであっても、fooを使用してスコープに暗黙のval b = new StringOrInt [Boolean]を定義するか、foo(2.9)(new StringOrInt [Double])を明示的に呼び出すことにより、クライアントコードでクラスを回避できます。クラスも抽象化する必要があると思います。
パオロファラベラ

2
はい; それはおそらく使用する方が良いでしょうtrait StringOrInt ...
機械式カタツムリ

7
Psサブタイプをサポートしたい場合は、単に(stackoverflow.com/questions/24387701/…を参照)に変更StringOrInt[T]してStringOrInt[-T]ください
Eran Medan

178

Miles Sabinは、彼の最近のブログ投稿でCurry-Howard同型を介してScalaでUnboxed union typesを取得する非常に優れた方法について説明しています。

彼は最初に型の否定を次のように定義します

type ¬[A] = A => Nothing

ドモルガンの法則を使用すると、これにより、組合のタイプを定義できます。

type[T, U] = ¬[¬[T] with ¬[U]]

以下の補助的な構成で

type ¬¬[A] = ¬[¬[A]]
type ||[T, U] = { type λ[X] = ¬¬[X] <:< (T ∨ U) }

次のようにユニオンタイプを記述できます。

def size[T : (Int || String)#λ](t : T) = t match {
    case i : Int => i
    case s : String => s.length
}

13
それは私が見た中で最も素晴らしいものの一つです。
Submonoid

18

6
上記のコメントはそれ自体で答えになるはずです。これはMilesのアイデアの単なる実装ですが、Maven Centralのパッケージにうまく組み込まれ、どこかでビルドプロセスの何かに問題を引き起こす可能性がある(?)ユニコードシンボルがすべてありません。
ジム・ピヴァルスキー

2
その面白いキャラクターはブール否定です。
michid

1
当初、このアイデアは私にはあまりにも複雑に見えました。このスレッドで言及されているほぼすべてのリンクを読んで、そのアイデアとその実装の美しさに夢中になりました:-) ...しかし、これはまだ複雑なものだと感じています... Scalaから離れて。マイルスが言うように:「今、我々はそれを直接アクセス可能にするためにマーティンとアドリアンをせがむ必要があるだけです。」
Richard Gomes、

44

新しい実験的なScalaコンパイラーであるDottyは、共用体型(A | B)をサポートしているため、希望どおりの操作を実行できます。

def foo(xs: (String | Int)*) = xs foreach {
   case _: String => println("str")
   case _: Int => println("int")
}

1
これらの日のいずれか。
Michael Ahlers、2018年

5
ちなみに、Dottyは新しいscala 3です(数か月前に発表されました)。
6infinity8

1
2020
JulienD

31

ユニオンタイプをエンコードするRex Kerrの方法を次に示します。ストレートでシンプル!

scala> def f[A](a: A)(implicit ev: (Int with String) <:< A) = a match {
     |   case i: Int => i + 1
     |   case s: String => s.length
     | }
f: [A](a: A)(implicit ev: <:<[Int with String,A])Int

scala> f(3)
res0: Int = 4

scala> f("hello")
res1: Int = 5

scala> f(9.2)
<console>:9: error: Cannot prove that Int with String <:< Double.
       f(9.2)
        ^

出典: Scalaでユニオンタイプをエンコードする別の方法を提供するMiles Sabinによるこの優れたブログ投稿の下のコメント#27 。


6
残念ながら、このエンコーディングscala> f(9.2: AnyVal)は無効になる可能性があります。タイプチェッカーに合格します。
キプトンバロス2011

@キプトン:それは悲しいです。Miles Sabinのエンコーディングもこの問題の影響を受けますか?
missingfaktor 2011

9
Milesのコードの少し単純なバージョンがあります。彼は実際には厳密な「not」ではなく、関数の反変パラメーターの逆の意味を使用trait Contra[-A] {}しているため、すべての関数の代わりに何も使用できません。だからあなたは(空想的なユニコードなしで)のtype Union[A,B] = { type Check[Z] = Contra[Contra[Z]] <:< Contra[Contra[A] with Contra[B]] }ようなものを手に入れることができますdef f[T: Union[Int, String]#Check](t: T) = t match { case i: Int => i; case s: String => s.length }
Rex Kerr、

これは共用体型の継承問題を解決するかもしれませんか?stackoverflow.com/questions/45255270/...
jhegedus

うーん、私はそのサブタイプを実装することが可能ではないようですので、私は、このエンコーディングでの戻り値の型を作成することはできません、それを試してみましたstackoverflow.com/questions/45255270/...
jhegedus

18

次のようにダニエルのソリューションを一般化することが可能です。

sealed trait Or[A, B]

object Or {
   implicit def a2Or[A,B](a: A) = new Or[A, B] {}
   implicit def b2Or[A,B](b: B) = new Or[A, B] {}
}

object Bar {
   def foo[T <% String Or Int](x: T) = x match {
     case _: String => println("str")
     case _: Int => println("int")
   }
}

このアプローチの主な欠点は、

  • ダニエルが指摘したように、それは混合型のコレクション/可変引数を処理しません
  • 一致が完全ではない場合、コンパイラーは警告を発行しません
  • 一致に不可能なケースが含まれている場合、コンパイラーはエラーを発行しません
  • Eitherアプローチと同様に、さらに一般化するにOr3Or4、類似の、などの特性を定義する必要があります。もちろん、そのような特性を定義することは、対応するEitherクラスを定義することよりもはるかに簡単です。

更新:

Mitch Blevins 、非常によく似たアプローチを示し、それを2つ以上のタイプに一般化して、「吃音」と呼んでいる方法を示しています。


18

タイプリストの概念と、この分野でのMiles Sabinの作業の単純化を組み合わせることで、n-aryユニオンタイプの比較的クリーンな実装にちょっとつまづきました。誰かが別の回答で言及しています。

に対して¬[-A]反変であるAとすると、定義により、のようにA <: B記述でき ¬[B] <: ¬[A]、型の順序を逆転させます。

与えられたタイプABX、私たちが表現したいですX <: A || X <: B。contravarianceを適用すると、我々が得ます¬[A] <: ¬[X] || ¬[B] <: ¬[X]。順番にこの缶は、次のように表すこと¬[A] with ¬[B] <: ¬[X]の一つが中AまたはBのスーパータイプでなければなりませんXX(関数の引数を考える)そのもの。

object Union {
  import scala.language.higherKinds

  sealed trait ¬[-A]

  sealed trait TSet {
    type Compound[A]
    type Map[F[_]] <: TSet
  }

  sealed traitextends TSet {
    type Compound[A] = A
    type Map[F[_]] =}

  // Note that this type is left-associative for the sake of concision.
  sealed trait[T <: TSet, H] extends TSet {
    // Given a type of the form `∅ ∨ A ∨ B ∨ ...` and parameter `X`, we want to produce the type
    // `¬[A] with ¬[B] with ... <:< ¬[X]`.
    type Member[X] = T#Map[¬]#Compound[¬[H]] <:< ¬[X]

    // This could be generalized as a fold, but for concision we leave it as is.
    type Compound[A] = T#Compound[H with A]

    type Map[F[_]] = T#Map[F] ∨ F[H]
  }

  def foo[A : (∅ ∨ StringIntList[Int])#Member](a: A): String = a match {
    case s: String => "String"
    case i: Int => "Int"
    case l: List[_] => "List[Int]"
  }

  foo(42)
  foo("bar")
  foo(List(1, 2, 3))
  foo(42d) // error
  foo[Any](???) // error
}

harrah / upTListのsに見られるように、この考えをメンバー型の上限と組み合わせることに少し時間を費やしましたが、型境界の実装はこれまでのところ困難であることが証明されています。Map


1
これは素晴らしいです、ありがとう!以前のアプローチを試しましたが、これを共用体の一部としてジェネリック型で使用すると問題が発生し続けました。これが、ジェネリック型を処理できる唯一の実装でした。
Samer Adra

残念ながら、おそらく予想されることですが、Javaコードから共用体型を取り込むScalaメソッドを使用しようとしても、機能しません。エラー:(40、29)java:クラスConfigのメソッドsetValueは、指定されたタイプに適用できません。必須:X、scala.Predef。$ less $ colon $ less <UnionTypes.package。$ u00AC <java.lang.Object>、UnionTypes.package。$ u00AC <X >>が見つかりました:java.lang.String理由:推測できませんtype-variable(s)X(実際の引数リストと正式な引数リストは長さが異なります)
Samer Adra

この実装のいくつかの詳細についてはまだ完全には明確ではありません。たとえば、元の記事では否定を「タイプ¬[A] = A =>なし」と定義していましたが、このバージョンでは、「封印された特性¬[-A]」のみがあり、特性はどこにも拡張されていません。これはどのように作動しますか?
Samer Adra

@Samer Adraどちらの方法でも機能します。この記事ではFunction1、既存の反変型として使用しています。実装は必要ありません。必要なのは、適合性の証拠です(<:<)。
J Cracknell 2016

共用体型を受け入れるコンストラクターをどのように持つかについてのアイデアはありますか?
Samer Adra

13

型クラスソリューションは、おそらく暗黙的にここにアクセスする最も良い方法です。これは、Odersky / Spoon / Vennersの本で言及されているモノイドアプローチに似ています。

abstract class NameOf[T] {
  def get : String
}

implicit object NameOfStr extends NameOf[String] {
  def get = "str"
}

implicit object NameOfInt extends NameOf[Int] {
 def get = "int"
}

def printNameOf[T](t:T)(implicit name : NameOf[T]) = println(name.get)

次にREPLでこれを実行すると:

scala> printNameOf(1)
int

scala> printNameOf("sss")
str

scala> printNameOf(2.0f)
<console>:10: error: could not find implicit value for parameter nameOf: NameOf[
Float]
       printNameOf(2.0f)

              ^

私は間違っているかもしれませんが、これがOPが探していたものだとは思いません。OPは、型の素な和集合を表す可能性のあるデータ型について質問し、実行時にそれについてケース分析を行って、実際の型がどうなるかを確認していました。型クラスは純粋にコンパイル時の構造であるため、この問題は解決されません。
トムクロケット

5
本当頼まれて質問が種類ごとに異なる振る舞いを公開する方法だったが、オーバーロードなし。型クラスの知識がない場合(およびおそらくC / C ++にある程度触れている場合)、共用体型が唯一の解決策のようです。Scalaの既存のEitherタイプは、この信念を強化する傾向があります。Scalaの暗黙的関数を介して型クラスを使用することは、根本的な問題に対するより良い解決策ですが、比較的新しい概念であり、まだ広く知られていません。そのため、OPは、これらをunion型の可能な代替として考えることすら知りませんでした。
ケビンライト

これはサブタイピングで機能しますか?stackoverflow.com/questions/45255270/...
jhegedus

10

またはのいずれかの方法でOr[U,V]型パラメーターを制約するために使用できる型演算子が必要です。これが、できる限り近い定義です。XX <: UX <: V

trait Inv[-X]
type Or[U,T] = {
    type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}

使い方は次のとおりです。

// use

class A; class B extends A; class C extends B

def foo[X : (B Or String)#pf] = {}

foo[B]      // OK
foo[C]      // OK
foo[String] // OK
foo[A]      // ERROR!
foo[Number] // ERROR!

これはいくつかのScalaタイプのトリックを使用します。主なものは、一般化された型制約の使用です。型Uとを指定するVと、Scalaコンパイラは、U <:< VそれUがのサブタイプであることを証明できる場合にのみ、呼び出されたクラス(およびそのクラスの暗黙的なオブジェクト)を提供しVます。以下は、いくつかのケースで機能する一般化された型制約を使用した簡単な例です。

def foo[X](implicit ev : (B with String) <:< X) = {}

この例Xは、クラスB、a String、またはのインスタンスが、orのスーパータイプでもサブタイプでもないタイプを持っているB場合に機能しStringます。最初の2つのケースでは、and のwithキーワードの定義によって真です。そのため、Scalaは暗黙のオブジェクトを提供し、次のように渡されます。Scalaコンパイラは正しく受け入れ、(B with String) <: B(B with String) <: Stringevfoo[B]foo[String]

最後のケースでは、if U with V <: X、then、U <: Xまたはif であるという事実に依存していV <: Xます。それは直感的に本当のように思えます、そして私は単にそれを仮定しています。ときに、この単純な例が失敗した理由は、この仮定から明らかだXのスーパータイプまたはサブタイプがあるのいずれかBString:例えば、上記の例では、foo[A]間違って受け入れられているとfoo[C]間違って拒否されます。繰り返しますが、私たちが望むのは、変数の型の式のいくつかの種類がありUVあり、Xそれは正確に真であるとき、X <: UまたはX <: V

Scalaの反変の概念は、ここで役立ちます。特性を覚えていますtrait Inv[-X]か?それは、その型パラメータで反変であるためXInv[X] <: Inv[Y]場合にのみY <: X。つまり、上記の例を実際に機能するものに置き換えることができます。

trait Inv[-X]
def foo[X](implicit ev : (Inv[B] with Inv[String]) <:< Inv[X]) = {}

これ(Inv[U] with Inv[V]) <: Inv[X]は、上記と同じ仮定により、正確にInv[U] <: Inv[X]orの場合に式が真であり、Inv[V] <: Inv[X]反変の定義により、これがX <: Uorの場合に正確に真であるためですX <: V

パラメータ化可能な型BOrString[X]を宣言し、それを次のように使用することで、物事をもう少し再利用可能にすることができます。

trait Inv[-X]
type BOrString[X] = (Inv[B] with Inv[String]) <:< Inv[X]
def foo[X](implicit ev : BOrString[X]) = {}

Scalaは今のタイプを構築しようとするBOrString[X]すべてのためにXそれfooを使用して呼び出され、そして時に型が正確に構築されるXのいずれかのサブタイプであるBかがString。それは機能し、省略表記があります。以下の構文は同等であり(evメソッド本体implicitly[BOrString[X]]で単にではなくメソッド本体で参照する必要があることを除いてevBOrString型コンテキストとして使用されます

def foo[X : BOrString] = {}

私たちが本当に望んでいるのは、タイプコンテキストバインドを作成する柔軟な方法です。型コンテキストはパラメーター化可能な型でなければならず、パラメーター化可能な方法で作成する必要があります。値の関数をカレーするように、型の関数をカレーしようとしているようです。つまり、次のようなものを希望します。

type Or[U,T][X] = (Inv[U] with Inv[T]) <:< Inv[X]

これはScalaでは直接可能はありませんが、かなり近づくために使用できるトリックがあります。これにより、Or上記の定義が得られます。

trait Inv[-X]
type Or[U,T] = {
    type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}

ここでは、構造型とScalaのポンド演算子を使用Or[U,T]して、1つの内部型を持つことが保証されている構造型を作成します。これは奇妙な獣です。コンテキストを与えるために、関数def bar[X <: { type Y = Int }](x : X) = {}はサブクラスで定義されAnyRefたタイプを持つサブクラスで呼び出されなければなりませんY

bar(new AnyRef{ type Y = Int }) // works!

ポンド演算子を使用すると、内部の型を参照でき、型演算子にインフィックス表記Or[B, String]#pf使用すると、次の元の定義に到達します。Orfoo

def foo[X : (B Or String)#pf] = {}

特性の定義を回避するために、最初の型パラメーターで関数型が反変であるという事実を使用できますInv

type Or[U,T] = {
    type pf[X] = ((U => _) with (T => _)) <:< (X => _)
} 

これでA|B <: A|B|C問題を解決できますか?stackoverflow.com/questions/45255270/…わかりません。
jhegedus

8

このハックもあります:

implicit val x: Int = 0
def foo(a: List[Int])(implicit ignore: Int) { }

implicit val y = ""
def foo(a: List[String])(implicit ignore: String) { }

foo(1::2::Nil)
foo("a"::"b"::Nil)

型消去のあいまいさの回避(Scala)を参照してください。


stackoverflow.com/questions/3422336/…を参照してください。実際にはもっと簡単なハックがあり(implicit e: DummyImplicit)ます。型シグネチャの1つに追加するだけです。
Aaron Novstrup、2010

7

あなたは見てかかることがありますMetaScalaと呼ばれるものを持っています、OneOf。これはmatchステートメントではうまく機能しないが、高次関数を使用してマッチングをシミュレートできるという印象を受けます。たとえば、このスニペットを見てください。ただし、「シミュレートされたマッチング」の部分はコメントアウトされていることに注意してください。

さて、編集のために:あなたが説明するように、Either3、Either4などを定義することについて、何か悪いことはないと思います。これは本質的に、Scalaに組み込まれている標準の22タプルタイプのデュアルです。Scalaに分離型が組み込まれていて、おそらくのような構文がいくつかあれば、それは確かにすばらしいことです{x, y, z}


6

ファーストクラスのディスジョイントタイプは、別のサブタイプを持つ封印されたスーパータイプであり、これらの代替サブタイプへの分離型の望ましいタイプとの間の暗黙の変換であると考えています。

これはMiles Sabinのソリューションのコメント 33〜36に対応していると思います。そのため、使用サイトで使用できるファーストクラスのタイプですが、テストしていません。

sealed trait IntOrString
case class IntOfIntOrString( v:Int ) extends IntOrString
case class StringOfIntOrString( v:String ) extends IntOrString
implicit def IntToIntOfIntOrString( v:Int ) = new IntOfIntOrString(v)
implicit def StringToStringOfIntOrString( v:String ) = new StringOfIntOrString(v)

object Int {
   def unapply( t : IntOrString ) : Option[Int] = t match {
      case v : IntOfIntOrString => Some( v.v )
      case _ => None
   }
}

object String {
   def unapply( t : IntOrString ) : Option[String] = t match {
      case v : StringOfIntOrString => Some( v.v )
      case _ => None
   }
}

def size( t : IntOrString ) = t match {
    case Int(i) => i
    case String(s) => s.length
}

scala> size("test")
res0: Int = 4
scala> size(2)
res1: Int = 2

1つの問題は、Scalaが大文字と小文字を区別するコンテキスト、つまりIntOfIntOrStringtoからInt(およびStringOfIntOrStringtoへString)の暗黙的な変換を採用しないため、エクストラクターを定義してのcase Int(i)代わりに使用する必要があることcase i : Intです。


追加:私は彼のブログでMiles Sabinに次のように返信しました。おそらく、次のいずれかに対していくつかの改善点があります。

  1. それは、使用または定義サイトで追加のノイズなしで、2種類以上に拡張されます。
  2. 引数は暗黙的にボックス化されます。たとえば、size(Left(2))or は必要ありませんsize(Right("test"))
  3. パターンマッチングの構文は、暗黙的にボックス化されていません。
  4. ボックス化とボックス化解除は、JVMホットスポットによって最適化されます。
  5. 構文は、将来のファーストクラスのユニオンタイプで採用される構文になる可能性があるため、移行はおそらくシームレスでしょうか?おそらくユニオンタイプ名の場合、たとえば、 ` `、 ` `、または私のお気に入りの ` `のV代わりに使用した方がよいでしょう。OrIntVStringInt |v| StringInt or StringInt|String

更新:上記のパターンの論理和の否定は次のとおりであり、Miles Sabinのブログに代替(そしておそらくより有用な)パターン追加しました

sealed trait `Int or String`
sealed trait `not an Int or String`
sealed trait `Int|String`[T,E]
case class `IntOf(Int|String)`( v:Int ) extends `Int|String`[Int,`Int or String`]
case class `StringOf(Int|String)`( v:String ) extends `Int|String`[String,`Int or String`]
case class `NotAn(Int|String)`[T]( v:T ) extends `Int|String`[T,`not an Int or String`]
implicit def `IntTo(IntOf(Int|String))`( v:Int ) = new `IntOf(Int|String)`(v)
implicit def `StringTo(StringOf(Int|String))`( v:String ) = new `StringOf(Int|String)`(v)
implicit def `AnyTo(NotAn(Int|String))`[T]( v:T ) = new `NotAn(Int|String)`[T](v)
def disjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `Int or String`) = x
def negationOfDisjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `not an Int or String`) = x

scala> disjunction(5)
res0: Int|String[Int,Int or String] = IntOf(Int|String)(5)

scala> disjunction("")
res1: Int|String[String,Int or String] = StringOf(Int|String)()

scala> disjunction(5.0)
error: could not find implicit value for parameter ev: =:=[not an Int or String,Int or String]
       disjunction(5.0)
                  ^

scala> negationOfDisjunction(5)
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
       negationOfDisjunction(5)
                            ^

scala> negationOfDisjunction("")
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
       negationOfDisjunction("")
                            ^
scala> negationOfDisjunction(5.0)
res5: Int|String[Double,not an Int or String] = NotAn(Int|String)(5.0)

別の更新:Mile Sabinのソリューションのコメント23と35について、使用サイトでユニオンタイプを宣言する方法を次に示します。最初のレベルの後にはボックス化されていないことに注意してください。つまり、分離の任意の数の型に拡張できるという利点がありますが、Eitherネストされたボックス化が必要で、以前のコメント41のパラダイムは拡張できませんでした。言い換えると、a D[Int ∨ String]はa に割り当て可能です(つまり、aのサブタイプです)D[Int ∨ String ∨ Double]

type ¬[A] = (() => A) => A
type[T, U] = ¬[T] with ¬[U]
class D[-A](v: A) {
  def get[T](f: (() => T)) = v match {
    case x : ¬[T] => x(f)
  }
}
def size(t: D[IntString]) = t match {
  case x: D[¬[Int]] => x.get( () => 0 )
  case x: D[¬[String]] => x.get( () => "" )
  case x: D[¬[Double]] => x.get( () => 0.0 )
}
implicit def neg[A](x: A) = new D[¬[A]]( (f: (() => A)) => x )

scala> size(5)
res0: Any = 5

scala> size("")
error: type mismatch;
 found   : java.lang.String("")
 required: D[?[Int,String]]
       size("")
            ^

scala> size("hi" : D[¬[String]])
res2: Any = hi

scala> size(5.0 : D[¬[Double]])
error: type mismatch;
 found   : D[(() => Double) => Double]
 required: D[?[Int,String]]
       size(5.0 : D[?[Double]])
                ^

どうやらScalaコンパイラーには3つのバグがある。

  1. 宛先の選言で最初の型の後の型に対して正しい暗黙の関数を選択しません。
  2. D[¬[Double]]ケースが一致から除外されることはありません。

3。

scala> class D[-A](v: A) {
  def get[T](f: (() => T))(implicit e: A <:< ¬[T]) = v match {
    case x : ¬[T] => x(f)
  }
}
error: contravariant type A occurs in covariant position in
       type <:<[A,(() => T) => T] of value e
         def get[T](f: (() => T))(implicit e: A <:< ?[T]) = v match {
                                           ^

コンパイラーがA共変位置で許可しないため、getメソッドは入力タイプで適切に制約されていません。必要なのは証拠だけなので、それはバグであると主張する人もいるかもしれません。関数の証拠にアクセスすることはありません。そして、私case _getメソッドでテストしないことを選択したので、Optioninのボックスを解除する必要はありませんでしmatchsize()


2012年3月5日:以前の更新には改善が必要です。Miles Sabinのソリューションはサブタイピングで正しく機能しました。

type ¬[A] = A => Nothing
type[T, U] = ¬[T] with ¬[U]
class Super
class Sub extends Super

scala> implicitly[(SuperString) <:< ¬[Super]]
res0: <:<[?[Super,String],(Super) => Nothing] = 

scala> implicitly[(SuperString) <:< ¬[Sub]]
res2: <:<[?[Super,String],(Sub) => Nothing] = 

scala> implicitly[(SuperString) <:< ¬[Any]]
error: could not find implicit value for parameter
       e: <:<[?[Super,String],(Any) => Nothing]
       implicitly[(Super ? String) <:< ?[Any]]
                 ^

私の以前の更新の提案(ほぼ一流のunionタイプについて)はサブタイプを壊しました。

 scala> implicitly[D[¬[Sub]] <:< D[(SuperString)]]
error: could not find implicit value for parameter
       e: <:<[D[(() => Sub) => Sub],D[?[Super,String]]]
       implicitly[D[?[Sub]] <:< D[(Super ? String)]]
                 ^

問題はあるA(() => A) => Aが共変(戻りの型)と反変(関数の入力、またはこの場合は関数の入力である関数の戻り値)の両方の位置に表示されるため、置換は不変にしかできないことです。

A => Nothingが必要なのAは、反変の位置にしたいためだけであることに注意してください。これにより、のスーパーA タイプは、D[¬[A]]norのサブタイプではなくなりますD[¬[A] with ¬[U]]も参照)。必要なのは二重の逆分散だけなので、¬and を破棄しても、マイルの解と同等の結果を得ることができます。

trait D[-A]

scala> implicitly[D[D[Super]] <:< D[D[Super] with D[String]]]
res0: <:<[D[D[Super]],D[D[Super] with D[String]]] = 

scala> implicitly[D[D[Sub]] <:< D[D[Super] with D[String]]]
res1: <:<[D[D[Sub]],D[D[Super] with D[String]]] = 

scala> implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
error: could not find implicit value for parameter
       e: <:<[D[D[Any]],D[D[Super] with D[String]]]
       implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
                 ^

だから完全な修正はです。

class D[-A] (v: A) {
  def get[T <: A] = v match {
    case x: T => x
  }
}

implicit def neg[A](x: A) = new D[D[A]]( new D[A](x) )

def size(t: D[D[Int] with D[String]]) = t match {
  case x: D[D[Int]] => x.get[D[Int]].get[Int]
  case x: D[D[String]] => x.get[D[String]].get[String]
  case x: D[D[Double]] => x.get[D[Double]].get[Double]
}

Scalaの以前の2つのバグは残っていますが、3番目のバグは回避されT、現在は次のサブタイプに制限されています。A

サブタイピング作業が確認できます。

def size(t: D[D[Super] with D[String]]) = t match {
  case x: D[D[Super]] => x.get[D[Super]].get[Super]
  case x: D[D[String]] => x.get[D[String]].get[String]
}

scala> size( new Super )
res7: Any = Super@1272e52

scala> size( new Sub )
res8: Any = Sub@1d941d7

私は両方のために、ファーストクラスの交差点の種類は非常に重要であることを考えているセイロンがそれらを持っている理由の代わりにするので、と包摂するAnyとアンボクシングた手段matchに期待されるタイプのランタイムエラーを生成することができ、(のアンボクシング異種コレクション含みますa)論理和は型チェックできます(Scalaは私が指摘したバグを修正する必要があります)。ユニオンは、異種のコレクションにmetascalaの実験的なHList使用する複雑さよりも簡単です。


上記の3番目の項目は、Scalaコンパイラのバグではありません。もともとはバグとして番号を付けていなかったので、今日は不注意に編集してしまった(バグであると述べなかった元の理由を忘れて)。7回の編集制限に達しているため、投稿を再度編集しませんでした。
シェルビームーアIII

上記の#1のバグは、size関数の別の定式化で回避できます。
シェルビームーアIII

2番目の項目はバグではありません。Scalaは共用体型を完全には表現できません。リンクされたドキュメントはコードの別のバージョンを提供するため、入力としてsize受け入れD[Any]られなくなります。
シェルビームーアIII

:私はかなりこの回答を得ることはありません、これはまた、この質問に対する答えであるstackoverflow.com/questions/45255270/...
jhegedus

5

カレーハワードを理解しない場合は、もう少し簡単に理解できる方法があります。

type v[A,B] = Either[Option[A], Option[B]]

private def L[A,B](a: A): v[A,B] = Left(Some(a))
private def R[A,B](b: B): v[A,B] = Right(Some(b))  
// TODO: for more use scala macro to generate this for up to 22 types?
implicit def a2[A,B](a: A): v[A,B] = L(a)
implicit def b2[A,B](b: B): v[A,B] = R(b)
implicit def a3[A,B,C](a: A): v[v[A,B],C] = L(a2(a))
implicit def b3[A,B,C](b: B): v[v[A,B],C] = L(b2(b))
implicit def a4[A,B,C,D](a: A): v[v[v[A,B],C],D] = L(a3(a))
implicit def b4[A,B,C,D](b: B): v[v[v[A,B],C],D] = L(b3(b))    
implicit def a5[A,B,C,D,E](a: A): v[v[v[v[A,B],C],D],E] = L(a4(a))
implicit def b5[A,B,C,D,E](b: B): v[v[v[v[A,B],C],D],E] = L(b4(b))

type JsonPrimtives = (String v Int v Double)
type ValidJsonPrimitive[A] = A => JsonPrimtives

def test[A : ValidJsonPrimitive](x: A): A = x 

test("hi")
test(9)
// test(true)   // does not compile

はディジョンで同様のテクニックを使用しています


これはサブタイピングで機能しますか?私の直感:いいえ、でも私は間違っているかもしれません。stackoverflow.com/questions/45255270/...
jhegedus

1

まあ、それはすべて非常に賢いですが、あなたがあなたの主要な質問への答えが「いいえ」のさまざまな種類であることはすでに知っていると思います。Scalaはオーバーロードを異なる方法で処理します。そして、それは認められなければなりません。あなたが説明するよりも少しエレガントではありません。その一部はJavaの相互運用性によるもので、一部は型推論アルゴリズムのエッジのあるケースをヒットしたくないためであり、一部は単純にHaskellでないためです。


5
私はしばらくScalaを使用していますが、あなたが思っているほど知識が豊富でもスマートでもありません。この例では、ライブラリがソリューションを提供する方法を確認できます。次に、そのようなライブラリー(またはいくつかの代替手段)が存在するかどうかを疑問に思うことは理にかなっています。
Aaron Novstrup、2010

1

すでに素晴らしい答えをここに追加します。Miles Sabinの共用体タイプ(およびJoshのアイデア)に基づいて構築された要点を次に示しますが、それらを再帰的に定義するため、共用体に2を超えるタイプを含めることができます(def foo[A : UNil Or Int Or String Or List[String]

https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb

注:プロジェクトで上記を試した後、私はついにプレーンオールドサムタイプ(つまり、サブクラスを持つ封印されたトレイト)に戻ることにしました。Miles Sabinの共用体型は、型パラメーターを制限するのに最適ですが、共用体型を返す必要がある場合、それほど多くは提供されません。


これでA|C <: A|B|Cサブタイピングの問題を解決できますか?stackoverflow.com/questions/45255270/… それは、それA or Cがのサブタイプである必要があるが、タイプが(A or B) or C含まれていないA or Cため、少なくともこのエンコーディングでのA or Cサブタイプを作成することに希望がないということを意味するため、NOと感じA or B or Cます。 。 どう思いますか ?
jhegedus 2017

0

docsから、以下を追加しsealedます:

sealed class Expr
case class Var   (x: String)          extends Expr
case class Apply (f: Expr, e: Expr)   extends Expr
case class Lambda(x: String, e: Expr) extends Expr

sealedパーツに関して:

プログラムの他の部分(...)でExpr型を拡張するケースクラスをさらに定義することができます。この形式の拡張性は、基本クラスExprをシールして宣言することで除外できます。この場合、Exprを直接拡張するすべてのクラスは、Exprと同じソースファイルになければなりません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.