“Tür ayrılığı” (birleşim türleri) nasıl tanımlanır?


181

Aşırı yüklenmiş yöntemlerin çift tanımlarıyla ilgilenmek için önerilen bir yol , aşırı yüklemeyi kalıp eşleştirme ile değiştirmektir:

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

Bu yaklaşım, argümanlarına statik tip denetimi teslim etmemizi gerektirir foo. Yazabilmek çok daha hoş olurdu

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

Ben yakın ile alabilirsiniz Either, ancak ikiden fazla türde çirkin hızlıca alır:

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")
   }
}

Bu tanımlama gerektirecektir genel (zarif, verimli) çözüm gibi görünüyor Either3, Either4aynı amaca ulaşmak için alternatif bir çözümün, .... mu bilen var mı? Bildiğim kadarıyla Scala'da yerleşik bir "tip ayrılığı" yok. Ayrıca, yukarıda tanımlanan örtük dönüşümler standart kütüphanede bir yerde gizleniyor, böylece onları içe aktarabilir miyim?

Yanıtlar:


142

Belirli bir durumda, Any*aşağıdaki hile işe yaramaz, çünkü karışık türleri kabul etmez. Ancak, karışık türler de aşırı yükleme ile çalışmaz, bu istediğiniz şey olabilir.

İlk olarak, aşağıdaki gibi kabul etmek istediğiniz türlerle bir sınıf bildirin:

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

Sonra şöyle beyan edin foo:

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

Ve bu kadar. foo(5)Ya da çağırabilirsiniz foo("abc")ve işe yarayacaktır, ancak deneyin foo(true)ve başarısız olacaktır. Bu StringOrInt[Boolean], aşağıdaki Randall tarafından belirtildiği gibi, StringOrIntbirsealed sınıf .

Bu T: StringOrInt, örtük bir tür parametresi olduğu StringOrInt[T]ve Scala'nın bir türdeki tamamlayıcı nesnelerin içine baktığı için, bu tür iş için kod soran kodların bulunup bulunmadığını görmek için çalışır.


14
Eğer class StringOrInt[T]yapılırsa sealed, bahsettiğiniz "sızıntı" ("Tabii ki, bu bir StringOrInt[Boolean]" oluşturarak istemci kodu tarafından yan basamaklı olabilir "), en azından StringOrIntkendi dosyasında bulunuyorsa takılır . O zaman tanık nesneleri aynı sosta tanımlanmalıdır StringOrInt.
Randall Schulz

3
Bu çözümü biraz genelleştirmeyi denedim (aşağıda bir cevap olarak gönderildi). EitherYaklaşıma kıyasla en büyük dezavantaj , maçı kontrol etmek için çok sayıda derleyici desteğini kaybettiğimiz gibi görünüyor.
Aaron Novstrup

iyi numara! Ancak, mühürlü sınıfla bile, foo kapsamında örtük bir val b = new StringOrInt [Boolean] tanımlayarak veya açıkça foo (2.9) (new StringOrInt [Double]) öğesini çağırarak istemci kodunda atlatabilirsiniz. Bence sınıfı da soyutlamanız gerekiyor.
Paolo Falabella

2
Evet; muhtemelen daha iyi olurdutrait StringOrInt ...
Mekanik salyangoz

7
Ps, alt türleri desteklemek istiyorsanız, sadece şuna StringOrInt[T]geçin StringOrInt[-T](bkz. Stackoverflow.com/questions/24387701/… )
Eran Medan

178

Miles Sabin, son blog yazısında Scala'daki Kutusuz sendika türlerini Curry-Howard izomorfizmasıyla birleştirmenin çok güzel bir yolunu anlatıyor :

Önce türlerin olumsuzlanmasını şöyle tanımlar:

type ¬[A] = A => Nothing

De Morgan yasasını kullanarak sendika türlerini tanımlamasına izin verir

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

Aşağıdaki yardımcı yapılar ile

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

sendika türlerini aşağıdaki gibi yazabilirsiniz:

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

13
Bu gördüğüm en harika şeylerden biri.
Submonoid


6
Yukarıdaki yorum kendi başına bir cevap olmalıdır. Bu sadece Miles'ın fikrinin bir uygulamasıdır, ancak Maven Central'daki bir pakete güzelce sarılmıştır ve tüm bu unicode sembolleri olmadan (?) Bir yerde bir inşa sürecindeki bir şey için bir sorun oluşturabilir.
Jim Pivarski

2
Bu komik karakter boolean negatifidir .
michid

1
Başlangıçta, fikir bana çok kıvrımlı görünüyordu. Bu iş parçacığında bahsedilen hemen hemen her bağlantıyı okurken, fikrin ve uygulamasının güzelliğiyle yakalandım :-) ... ama hala bunun kıvrımlı bir şey olduğunu hissediyorum ... şimdi sadece düz olmadığı için Scala'dan uzakta. Miles'ın dediği gibi: "Şimdi doğrudan erişilebilir hale getirmek için Martin ve Adriaan'ı rahatsız etmemiz gerekiyor."
Richard Gomes

44

Yeni bir deneysel Scala derleyicisi olan Dotty , sendika türlerini (yazılı A | B) desteklemektedir , böylece tam olarak istediğinizi yapabilirsiniz:

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

1
Bu günlerden biri.
Michael Ahlers

5
Bu arada, Dotty yeni scala 3 olacak (birkaç ay önce ilan edildi).
6infinity8

1
ve 2020 sonlarında bir yerde satışa sunulacak
JulienD

31

Sendika türlerini kodlamanın Rex Kerr yolu. Düz ve basit!

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)
        ^

Kaynak: Miles Sabin tarafından Scala'daki sendika türlerini kodlamanın başka bir yolunu sunan bu mükemmel blog yazısı altındaki 27 numaralı yorum .


6
Ne yazık ki, bu kodlama yenilebilir: scala> f(9.2: AnyVal)daktilodan geçer.
Kipton Barros

@Kipton: Çok üzücü. Miles Sabin'in kodlaması da bu sorundan muzdarip mi?
missingfaktor

9
Miles kodunun biraz daha basit bir sürümü var; aslında fonksiyonun karşıt değişken parametresinin ters anlamını kullandığından, katı bir "değil" olduğundan, trait Contra[-A] {}tüm fonksiyonların yerine hiçbir şeye kullanamazsınız . Yani gibi type Union[A,B] = { type Check[Z] = Contra[Contra[Z]] <:< Contra[Contra[A] with Contra[B]] }kullanılan şeyler olsun def f[T: Union[Int, String]#Check](t: T) = t match { case i: Int => i; case s: String => s.length }(fantezi unicode olmadan).
Rex Kerr

Bu sendika tiplerinin miras sorununu çözebilir mi? stackoverflow.com/questions/45255270/…
jhegedus

Hmm, denedim, bu kodlamalar ile dönüş türleri oluşturamıyorum, bu yüzden alt
türünü kullanmak

18

Daniel'in çözümünü şu şekilde genelleştirmek mümkündür :

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")
   }
}

Bu yaklaşımın ana dezavantajları

  • Daniel'in işaret ettiği gibi, karışık tiplerle koleksiyonları / varargs'ı işlemez
  • Maç ayrıntılı değilse derleyici uyarı vermez
  • Eşleşme imkansız bir durum içeriyorsa derleyici hata vermez
  • Gibi Eitherbir yaklaşımla, daha ileri genelleme benzer tanımlayan gerektirecektir Or3, Or4vb özellikleri. Tabii ki, bu tür özellikleri tanımlamak, karşılık gelen Eithersınıfları tanımlamaktan çok daha kolay olacaktır .

Güncelleme:

Mitch Blevins gösteren çok benzer bir yaklaşım onu "kekeleme ya da" dublaj, ikiden fazla türleri için genelleme nasıl ve gösteriler.


18

N-ary sendika türlerinin nispeten temiz bir şekilde uygulanması, tür listeleri kavramını, birisinin başka bir cevapta bahsettiği Miles Sabin'in bu alandaki çalışmasını basitleştirerek birleştirerek bir tür tökezledim .

¬[-A]Üzerinde çelişkili tip göz önüne alındığında A, tanım gereği A <: Byazabiliriz ¬[B] <: ¬[A] , türlerin sırasını tersine çeviririz.

Verilen tipler A, Bve X, ifade etmek istiyoruz X <: A || X <: B. Karşıtlık uygulayarak anlıyoruz ¬[A] <: ¬[X] || ¬[B] <: ¬[X]. Bu da kendisinden birinin veya kendisinin ¬[A] with ¬[B] <: ¬[X]bir üst tipi olduğu Aveya Bolması gerektiği şeklinde ifade edilebilir (işlev argümanlarını düşünün).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
}

Ben harrah / upTList s görüldüğü gibi üye türleri üzerinde bir üst sınırı ile bu fikri birleştirmek için biraz zaman harcadım , ancak tip sınırları ile uygulanması bugüne kadar zor oldu.Map


1
Bu harika, teşekkürler! Daha önceki yaklaşımları denedim ama bunu birliğin parçası olarak genel türlerle kullanma konusunda sorun yaşamaya devam ettim. Genel türlerle çalışabildiğim tek uygulama buydu.
Samer Adra

Ne yazık ki, ama muhtemelen beklenen, Java kodundan bir sendika türü alan bir Scala yöntemi kullanmaya çalıştığımda, çalışmıyor. Hata: (40, 29) java: method setValue sınıfındaki yapılandırma Verilen türlere uygulanamıyor; gerekli: X, scala.Predef. $ less $ colon $ less <UnionTypes.package. $ u00AC <java.lang.Object>, UnionTypes.package. $ u00AC <X>> bulundu: java.lang.String nedeni: çıkarım yapamıyor type-değişken (ler) X (gerçek ve resmi bağımsız değişken listeleri uzunluk olarak farklılık gösterir)
Samer Adra

Bu uygulamadaki bazı detaylarda hala net değil. Örneğin, orijinal makale olumsuzlamayı "tip ¬ [A] = A => Hiçbir şey" olarak tanımlamıştır, ancak bu sürümde "mühürlü özellik ¬ [-A]" varsa ve özellik hiçbir yerde uzatılmamışsa. Bu nasıl çalışıyor?
Samer Adra

@Samer Adra Her iki şekilde de çalışır, makale Function1mevcut bir karşıt değişken türü olarak kullanır . Bir uygulamaya ihtiyacınız yoktur, tek ihtiyacınız olan uygunluk kanıtıdır ( <:<).
J Cracknell

Sendika tipini kabul eden bir kurucuya nasıl sahip olacağınız hakkında bir fikriniz var mı?
Samer Adra

13

Tip sınıfı bir çözüm, muhtemelen sonuçları kullanarak buraya gitmenin en güzel yoludur. Bu, Odersky / Spoon / Venners kitabında belirtilen monoid yaklaşıma benzer:

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)

Bunu REPL'de çalıştırırsanız:

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)

              ^

Yanlış olabilirdim, ama OP'nin aradığı şey bu değil. OP, ayrık bir tür birleşimini temsil edebilecek bir veri türü soruyordu ve daha sonra gerçek türün ne olduğunu görmek için çalışma zamanında vaka analizi yapıyordu . Tür sınıfları bu sorunu çözmez, çünkü bunlar tamamen derleme zamanı bir yapıdır.
Tom Crockett

5
Gerçek isteniyor soru nasıl ama aşırı yüklemeden farklı türleri için farklı davranış ortaya koymaktır. Tür sınıfları (ve belki de C / C ++ 'a maruz kalma) bilgisi olmadan, birleştirme türü tek çözüm gibi görünmektedir. Scala'nın önceden var olan Eithertürü bu inancı pekiştirme eğilimindedir. Scala'nın etkileri yoluyla tip sınıflarını kullanmak altta yatan soruna daha iyi bir çözümdür, ancak nispeten yeni bir kavramdır ve hala yaygın olarak bilinmemektedir, bu yüzden OP onları bir sendika tipine olası bir alternatif olarak görmeyi bile bilmiyordu.
Kevin Wright

bu alt tiplerle çalışır mı? stackoverflow.com/questions/45255270/…
jhegedus

10

Biz bir tür operatörü istiyorum Or[U,V]bir tür parametreleri sınırlamak için kullanılabilir Xşekilde birinde X <: Uveya X <: V. İşte alabildiğimiz kadar yaklaşan bir tanım:

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

İşte nasıl kullanılır:

// 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!

Bu birkaç Scala tipi numara kullanır. Ana olan kullanımı genelleştirilmiş tip kısıtlamalarının kullanılmasıdır . Verilen türler Uve VScala derleyicisi, U <:< Vancak Scala derleyicisi Ubunun bir alt türü olduğunu kanıtlayabilirse (ve bu sınıfın örtük bir nesnesi) adlı bir sınıf sağlar V. Aşağıda, bazı durumlarda işe yarayan genelleştirilmiş tür kısıtlamalarını kullanan daha basit bir örnek verilmiştir:

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

Bu örnek , bir Xsınıf B, a Stringveya bir üst türü Bveya alt türü olmayan bir türe sahip olduğunda işe yarar String. İlk iki durumda, withanahtar kelimenin tanımıyla doğrudur (B with String) <: Bve (B with String) <: Stringbu nedenle Scala, şu şekilde aktarılacak örtük bir nesne sağlayacaktır ev: Scala derleyicisi doğru bir şekilde kabul eder foo[B]ve foo[String].

Son durumda, eğer U with V <: X, o zaman U <: Xya da V <: X. Sezgisel olarak doğru görünüyor ve sadece varsayıyorum. Zaman bu basit bir örnek başarısız neden bu varsayımı da görüleceği üzere Xya bir üst tip veya alt tipidir BveyaString : örneğin, yukarıdaki örnekte, foo[A]yanlış kabul edilir ve foo[C]yanlış reddedilir. Tekrardan, ne istediğimiz değişkenlere tipi ifade çeşit U, Vve Xbu tam olarak doğru olduğunda X <: UveyaX <: V .

Scala'nın karşıtlık kavramı burada yardımcı olabilir. Özelliği hatırlatrait Inv[-X] musunuz? Çünkü onun türü parametresinde X, Inv[X] <: Inv[Y]sadece ve yalnızca kontravaryanttır Y <: X. Bu, yukarıdaki örneği gerçekten işe yarayacak bir örnekle değiştirebileceğimiz anlamına gelir:

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

Çünkü ifade (Inv[U] with Inv[V]) <: Inv[X] tam olarak ne zaman, yukarıdaki aynı varsayımı ile, doğrudur Inv[U] <: Inv[X]ya Inv[V] <: Inv[X]ve contravariance tanımına göre, bu tam olarak ne zaman doğrudur X <: Uveya X <: V.

Parametrelendirilebilir bir tür bildirerek BOrString[X]ve aşağıdaki gibi kullanarak işleri biraz daha yeniden kullanılabilir hale getirmek mümkündür :

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

Scala şimdi tipini inşa dener BOrString[X]her için Xo fooile denir ve ne zaman tipi kesin inşa edilecek Xya bir alt tipi olan Bveya String. Bu işe yarıyor ve steno gösterimi var. Aşağıdaki sözdizimi eşdeğerdir (bunun dışında evartık yöntem gövdesinde implicitly[BOrString[X]]değil, basitçe değil ev)BOrString bir şekilde türü bağlamında bağlanmış :

def foo[X : BOrString] = {}

Gerçekten istediğimiz, bir bağlam bağlamı oluşturmak için esnek bir yoldur. Bir tür bağlamı parametrelenebilir bir tür olmalıdır ve bir tane oluşturmak için parametrelenebilir bir yol istiyoruz. Bu, değerler üzerindeki işlevleri körüklediğimiz gibi, türlerdeki işlevleri körüklemeye çalıştığımız anlaşılıyor. Başka bir deyişle, aşağıdaki gibi bir şey istiyoruz:

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

Bu doğrudan mümkün değil Scala, ama biz oldukça yakın almak için kullanabileceğiniz bir hile var. Bu bizi Oryukarıdaki tanımlamaya getiriyor :

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

Burada yapısal tipleme ve Scala'nın kiloluk operatörünüOr[U,T] tek bir dahili tipte olması garanti edilen bir yapısal tip oluşturmak için kullanıyoruz . Bu garip bir canavar. Bir bağlam vermek için, fonksiyonun bir tür def bar[X <: { type Y = Int }](x : X) = {}alt sınıfı ile çağrılması gerekirAnyRefY tanımlanmış :

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

Pound operatörünü kullanmak iç tipe bakmamıza izin verir ve tip operatörü için infix gösteriminiOr[B, String]#pf kullanarak orijinal tanımımıza ulaşırız :Orfoo

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

Özelliği tanımlamaktan kaçınmak için işlev türlerinin ilk tür parametrelerinde çelişkili olduğu gerçeğini kullanabiliriz Inv:

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

Bu sorunu çözebilir A|B <: A|B|Cmi? stackoverflow.com/questions/45255270/… Söyleyemem.
jhegedus


7

Adı verilen MetaScala'ya bir göz atabilirsinizOneOf . Bunun matchifadelerle iyi çalışmadığı, ancak üst düzey işlevleri kullanarak eşleştirmeyi simüle edebileceğiniz izlenimi edindim. Örneğin bu snippet'e bir göz atın , ancak belki de henüz tam olarak çalışmadığı için "benzetilmiş eşleme" kısmının yorumlandığına dikkat edin.

Şimdi bazı editoryalleştirmeler için: Açıkladığınız gibi Either3, Either4, vb. Tanımlamak konusunda berbat bir şey olduğunu düşünmüyorum. Bu aslında Scala'da yerleşik olan standart 22 demet tipine göre ikidir. Scala'da yerleşik ayrık tipler ve belki de onlar için hoş bir sözdizimi olsaydı kesinlikle iyi olurdu {x, y, z}.


6

Birinci sınıf ayrık tipin, alternatif alt tiplerle ve bu alternatif alt tiplere istenen ayrıklık tiplerine / bu tiplerden örtük dönüşümlerle kapalı bir süper tip olduğunu düşünüyorum.

Bu , Miles Sabin'in çözümünün 33 - 36 yorumlarını ele aldığını varsayıyorum , bu nedenle kullanım sitesinde kullanılabilecek birinci sınıf türü, ancak test etmedim.

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

Bir sorun, Scala'nın eşleştirme bağlamında işe yaramayacağı, ' IntOfIntOrStringden Int(ve' StringOfIntOrStringye String) örtük bir dönüşüm olduğu için çıkarıcılar tanımlaması ve case Int(i)bunun yerine kullanılması gerekir case i : Int.


ADD: Miles Sabin'e blogunda şöyle cevap verdim. Belki de İkisi üzerinde birkaç gelişme var:

  1. Kullanım veya tanımlama sitesinde herhangi bir ek gürültü olmadan 2'den fazla türe uzanır.
  2. Bağımsız değişkenler örtük olarak kutlanır, örn . Gerek yok size(Left(2))veya size(Right("test")).
  3. Desen eşleşmesinin sözdizimi örtülü olarak kutudan çıkarılır.
  4. Boks ve kutudan çıkarma JVM etkin noktası tarafından optimize edilebilir.
  5. Sözdizimi, gelecekteki birinci sınıf sendika türü tarafından benimsenen sözdizimi olabilir, bu nedenle göç belki de kesintisiz olabilir mi? Belki sendika türü adı için , örneğin ` `, ` ` veya en sevdiğim ` ` Vyerine kullanmak daha iyi olur ?OrIntVStringInt |v| StringInt or StringInt|String

GÜNCELLEME: Yukarıdaki örüntü için kopukluğun mantıksal olarak reddedilmesi takip eder ve Miles Sabin'in bloguna alternatif (ve muhtemelen daha kullanışlı) bir örüntü ekledim .

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)

BAŞKA BİR GÜNCELLEME: Mile Sabin'in çözümünün 23 ve 35. yorumlarıyla ilgili olarak, kullanım sahasında sendika türünü bildirmenin bir yolu. İlk seviyeden sonra kutunun açıldığına dikkat edin, yani avantajın kopmadaki herhangi bir sayı tipine genişletilebilir olması gerekirken, Eitheriç içe boksa ihtiyaç duyar ve önceki yorumumdaki 41 paradigma genişletilemezdi. Başka bir deyişle, a D[Int ∨ String], a'ya atanabilir (yani bir alt tipidir) 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]])
                ^

Görünüşe göre Scala derleyicisinin üç hatası var.

  1. Hedef disjunctiondaki ilk türden sonra herhangi bir tür için doğru örtük işlevi seçmez.
  2. Bu dışlamaz D[¬[Double]]maçından dava.

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 {
                                           ^

ADerleme kovaryant pozisyonuna izin vermeyeceğinden , get yöntemi giriş türünde doğru şekilde kısıtlanmamıştır . Birisi bunun bir hata olduğunu iddia edebilir, çünkü istediğimiz tek şey kanıttır, fonksiyondaki kanıtlara asla erişemeyiz. Ve ben değil testine seçim yapmış case _içinde getben bir Unbox olmazdı böylece yöntemde Optiondematch içinde size().


05 Mart 2012: Önceki güncellemenin iyileştirilmesi gerekiyor. Miles Sabin'in çözümü alt tiplerle doğru çalıştı.

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]]
                 ^

Önceki güncellememin önerisi (neredeyse birinci sınıf sendika türü için) alt türlemeyi kırdı.

 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)]]
                 ^

Problem şu A de (() => A) => Abildirdiğinden (dönüş türü) ve kontravaryant hem görünür pozisyonlar (fonksiyon girişi, veya bu durumda bir fonksiyonu girişi olan fonksiyonunun bir geri dönüş değeri) ve bu nedenle ikameler değişmez olabilir.

Not A => Nothingistediğimiz tek nedeni gereklidir Akontravaryant pozisyonda, yani bir supertypes o A değil alt tipler arasında D[¬[A]]ne de D[¬[A] with ¬[U]]( ayrıca bkz ). Biz sadece çift contravariance ihtiyaç beri atmak bile, biz Miles'in çözümü denk elde edebilirsiniz ¬ve .

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]]]
                 ^

Yani tam düzeltme.

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'daki önceki 2 hatanın kaldığına dikkat edin, ancak 3. hatadan kaçınılmalıdır. T şimdi alt tip olarak kısıtlandığı içinA .

Alt tip çalışmaları onaylayabiliriz.

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

Ben birinci sınıf kavşak tipleri, çok önemli olduğunu düşünen olmuştur hem Seylan onları sahip olmasının nedenlerinden yerine çünkü ve subsuming için Anybir ile unboxing hangi yollarla matchbir çalışma zamanı hatası oluşturabilir beklenen türlerinde, bir (bir kutudan çıkarma heterojen koleksiyonu içeren a) Ayrılma tipi kontrol edilebilir (Scala not ettiğim hataları düzeltmek zorundadır). Sendikalar daha basittir kullanarak karmaşıklığı deneysel hList ait metascala heterojen koleksiyonları için.


Yukarıdaki 3 numaralı öğe Scala derleyicisindeki bir hata değildir . Ben başlangıçta bir hata olarak numaralandırmamıştı, sonra dikkatsizce bugün bir düzenleme yaptım ve bunu yaptım (bir hata olduğunu belirtmek için orijinal nedenimi unutmak). Gönderiyi tekrar düzenlemedim, çünkü 7 düzenleme sınırındayım.
Shelby Moore III

Yukarıdaki 1 numaralı hata , sizefonksiyonun farklı bir formülasyonu ile önlenebilir .
Shelby Moore III

# 2 öğesi bir hata değildir. Scala sendika türünü tam olarak ifade edemez . Bağlı belge, kodun başka bir sürümünü sağlar, böylece sizeartık D[Any]girdi olarak kabul edilmez.
Shelby Moore III

Bu yanıtı tam olarak almıyorum, bu da bu sorunun cevabı mı: stackoverflow.com/questions/45255270/…
jhegedus

5

Curry-Howard'ı sevmezseniz anlamanız biraz daha kolay olan başka bir yol daha vardır:

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

Dijon'da benzer bir teknik kullanıyorum


Bu alt tiplerle çalışabilir mi? Bağırsak hissim: hayır, ama yanlış olabilirim. stackoverflow.com/questions/45255270/…
jhegedus

1

Hepsi bu kadar zekice, ama eminim ki zaten önde gelen sorularınızın cevaplarının çeşitli "Hayır" çeşitleri olduğunu biliyorsunuzdur. Scala aşırı yüklenmeyi farklı şekilde ele alır ve kabul ettiğinizden, tarif ettiğinizden daha az zarif bir şekilde kabul edilmelidir. Bunun bir kısmı Java ile birlikte çalışabilirlik nedeniyle, bazıları da tür çıkarım algoritmasının kenarlı vakalarını vurmak istememesinden kaynaklanıyor ve bunun bir kısmı sadece Haskell olmamasından kaynaklanıyor.


5
Scala'yı bir süredir kullanırken, ne kadar bilgili ne de düşündüğünüz kadar akıllıyım. Bu örnekte, bir kütüphanenin çözümü nasıl sağlayabildiğini görebiliyorum. O zaman böyle bir kütüphanenin (ya da bir alternatifin) var olup olmadığını merak etmek mantıklıdır.
Aaron Novstrup

1

Zaten harika cevaplara burada ekliyoruz. İşte Miles Sabin sendika türlerine (ve Josh'un fikirlerine) dayanan, aynı zamanda bunları tekrar tekrar tanımlayan bir özgeçmiş, böylece sendikada> 2 tipiniz olabilir ( def foo[A : UNil Or Int Or String Or List[String])

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

Not: Bir proje için yukarıdakilerle oynadıktan sonra, düz eski-toplam türlerine (yani alt sınıflarla mühürlü özellik) geri döndüğümü eklemeliyim. Miles Sabin birleşim tipleri, type parametresini kısıtlamak için mükemmeldir, ancak birleşim türünü döndürmeniz gerekiyorsa, fazla bir şey sunmaz.


Bu A|C <: A|B|Calt tipleme sorununu çözebilir mi? stackoverflow.com/questions/45255270/… Benim bağırsak HAYIR hissediyorum çünkü o zaman bunun A or Calt türü olması gerektiği anlamına gelir , (A or B) or Cancak bu tür içermez, A or Cbu nedenle en azından bu kodlama ile A or Cbir alt tür yapmak için bir umut yoktur A or B or C.. . ne düşünüyorsun ?
jhegedus

0

Gönderen dokümanlar eklenmesiyle, 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

Bölüm ile ilgili olarak sealed:

Programın diğer bölümlerinde Expr türünü genişleten başka vaka sınıfları tanımlamak mümkündür (...). Bu genişletilebilirlik biçimi, Expr mühürlü temel sınıfın bildirilmesi; bu durumda, doğrudan Expr'i genişleten tüm sınıfların Expr ile aynı kaynak dosyasında olması gerekir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.