วิธีการกำหนด“ การแยกประเภท” (ประเภทยูเนี่ยน)?


181

วิธีหนึ่งที่ได้รับการแนะนำให้จัดการกับคำจำกัดความที่ซ้ำซ้อนของวิธีโอเวอร์โหลดคือการแทนที่การโอเวอร์โหลดด้วยการจับคู่รูปแบบ:

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แต่มันก็น่าเกลียดเร็วกว่าสองประเภท:

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

ดูเหมือนทั่วไป (สง่างามที่มีประสิทธิภาพ) วิธีการแก้ปัญหาจะต้องมีการกำหนดEither3, Either4, .... ไม่มีใครรู้วิธีการแก้ปัญหาอื่นเพื่อให้บรรลุวัตถุประสงค์เดียวกันได้หรือไม่ สำหรับความรู้ของฉัน 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]เว้นแต่ที่ระบุไว้โดยแรนดัลด้านล่างให้คุณระดับStringOrIntsealed

มันใช้งานได้เพราะT: StringOrIntหมายความว่ามีพารามิเตอร์ประเภทโดยปริยายStringOrInt[T]และเนื่องจาก Scala ดูภายในวัตถุที่เป็นประเภทสหายเพื่อดูว่ามีนัยในการสร้างรหัสเพื่อขอรหัสประเภทนั้นหรือไม่


14
ถ้าclass StringOrInt[T]ทำsealed"รั่วไหล" ที่คุณอ้างถึง ("แน่นอนว่านี่อาจเป็นไปได้ที่โค้ดไคลเอ็นต์โดยการสร้างStringOrInt[Boolean]") ถูกเสียบอย่างน้อยถ้าStringOrIntอยู่ในไฟล์ของตัวเอง จากนั้นวัตถุพยานที่จะต้องกำหนดไว้ใน souce StringOrIntเดียวกับ
แรนดัลชูลซ์

3
ฉันลองใช้วิธีทั่วไปในการแก้ปัญหานี้บ้าง (โพสต์เป็นคำตอบด้านล่าง) ข้อเสียเปรียบหลักเมื่อเทียบกับEitherวิธีการดูเหมือนว่าเราจะสูญเสียการสนับสนุนคอมไพเลอร์จำนวนมากสำหรับการตรวจสอบการแข่งขัน
Aaron Novstrup

เคล็ดลับดี! อย่างไรก็ตามแม้จะมีคลาสที่ปิดผนึกคุณยังสามารถหลีกเลี่ยงได้ในรหัสลูกค้าโดยการกำหนดค่า val b = new StringOrInt [Boolean] ในขอบเขตด้วย foo หรือโดยการเรียก foo (2.9) (new StringOrInt [Double] อย่างชัดเจน ฉันคิดว่าคุณต้องทำให้เป็นนามธรรมของคลาสด้วย
เปาโล Falabella

2
ใช่; มันน่าจะดีกว่าที่จะใช้trait StringOrInt ...
หอยทากเชิงกล

7
ป.ล. ถ้าคุณต้องการสนับสนุนประเภทย่อยเพียงแค่เปลี่ยนStringOrInt[T]เป็นStringOrInt[-T](ดูstackoverflow.com/questions/24387701/ … )
Eran Medan

178

Miles Sabin อธิบายวิธีที่ดีมากในการรับประเภทสหภาพในโพสต์บล็อกล่าสุด ประเภท Unboxed union ใน Scala ผ่าน isomorphism Curry-Howard :

เขากำหนดประเภทของการปฏิเสธเป็นครั้งแรก

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
นี่คือการดำเนินการตามแนวคิดของ Miles: github.com/GenslerAppsPod/scalavro/blob/master/util/src/main/ … - พร้อมตัวอย่าง: github.com/GenslerAppsPod/scalavro/blob/master/util/src/ ทดสอบ / …
คอนเนอร์ดอยล์

6
ความคิดเห็นข้างต้นควรเป็นคำตอบด้วยตัวเอง มันเป็นเพียงการนำความคิดของ Miles มารวมไว้ในแพ็คเกจบน Maven Central และไม่มีสัญลักษณ์ยูนิโค้ดทั้งหมดที่อาจ (?) สร้างปัญหาให้กับบางสิ่งในกระบวนการสร้างที่ไหนสักแห่ง
จิม Pivarski

2
ว่าตัวละครที่ตลกคือการปฏิเสธบูล
michid

1
ตอนแรกความคิดนั้นดูซับซ้อนเกินไปสำหรับฉัน การอ่านเกือบทุกลิงก์ที่กล่าวถึงในหัวข้อนี้ฉันถูกความคิดและโดยความงามของการใช้งาน :-) ... แต่ฉันยังคงรู้สึกเช่นนี้เป็นสิ่งที่ซับซ้อน ... ตอนนี้เพียงเพราะยังไม่สามารถใช้ได้ ห่างจากสกาล่า ดังที่ Miles กล่าวว่า: "ตอนนี้เราแค่ต้องรบกวน Martin และ Adriaan เพื่อให้สามารถเข้าถึงได้โดยตรง"
Richard Gomes

44

Dottyคอมไพเลอร์ Scala รุ่นทดลองรุ่นใหม่รองรับประเภทยูเนี่ยน (เขียนA | B) ดังนั้นคุณสามารถทำสิ่งที่คุณต้องการได้:

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

1
หนึ่งวันนี้
Michael Ahlers

5
โดยวิธีการ Dotty จะเป็นสกาล่า 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)
        ^

ที่มา:ความคิดเห็น # 27 ภายใต้การโพสต์บล็อกที่ยอดเยี่ยมนี้โดย Miles Sabin ซึ่งมีวิธีการเข้ารหัสประเภทสหภาพใน Scala อีกวิธีหนึ่ง


6
น่าเสียดายที่การเข้ารหัสนี้สามารถเอาชนะได้: scala> f(9.2: AnyVal)ส่งผ่านตัวตรวจสอบ
Kipton Barros

@ คิพตัน: มันน่าเศร้า การเข้ารหัสของ Miles Sabin ประสบปัญหานี้หรือไม่
missingfaktor

9
มีรหัส Miles รุ่นที่เรียบง่ายกว่าเล็กน้อย เนื่องจากที่จริงแล้วเขาใช้ความหมายย้อนกลับของพารามิเตอร์ contravariant ของฟังก์ชันไม่ใช่ข้อ จำกัด "ไม่" คุณจึงสามารถใช้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 }(โดยไม่ต้อง Unicode แฟนซี)
Rex Kerr

สิ่งนี้อาจแก้ปัญหาการสืบทอดประเภทสหภาพได้หรือไม่ stackoverflow.com/questions/45255270/…
jhegedus

อืมฉันพยายามแล้วฉันไม่สามารถสร้างประเภทผลตอบแทนด้วยการเข้ารหัสนี้ได้ดังนั้นจึงดูเหมือนว่าจะไม่สามารถนำ subtyping stackoverflow.com/questions/45255270//
jhegedus

18

เป็นไปได้ที่จะสรุปการแก้ปัญหาของ Danielดังนี้:

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

ข้อเสียเปรียบหลักของวิธีนี้คือ

  • ดังที่ Daniel ชี้ให้เห็นมันไม่ได้จัดการคอลเลกชัน / varargs ที่มีประเภทผสม
  • คอมไพเลอร์ไม่ออกคำเตือนหากการแข่งขันไม่ครบถ้วนสมบูรณ์
  • คอมไพเลอร์ไม่ได้ออกข้อผิดพลาดหากการแข่งขันรวมถึงกรณีที่เป็นไปไม่ได้
  • เช่นเดียวกับEitherวิธีการทั่วไปต่อไปจะต้องมีการกำหนดคล้ายOr3, Or4ฯลฯ ลักษณะ แน่นอนการกำหนดลักษณะดังกล่าวจะง่ายกว่าการนิยามEitherคลาสที่เกี่ยวข้อง

ปรับปรุง:

มิทช์บเลวินแสดงให้เห็นถึง วิธีการที่คล้ายกันมากและแสดงให้เห็นถึงวิธีการทำให้เป็นแบบทั่วไปมากกว่าสองประเภท


18

ฉันสะดุดในการใช้งานประเภทสหภาพที่สะอาดโดยการรวมแนวคิดของรายการประเภทเข้ากับการทำงานของMiles Sabin ที่เรียบง่ายในบริเวณนี้ซึ่งบางคนกล่าวถึงคำตอบอื่น

กำหนดประเภท¬[-A]ซึ่ง contravariant บนAตามคำจำกัดความให้A <: Bเราสามารถเขียน ¬[B] <: ¬[A]inverting เรียงลำดับของประเภท

ประเภทที่กำหนดA, Bและเราต้องการที่จะแสดงX X <: A || X <: Bการประยุกต์ใช้ contravariance ¬[A] <: ¬[X] || ¬[B] <: ¬[X]ที่เราได้รับ สามารถเปิดนี้จะแสดงเป็น¬[A] with ¬[B] <: ¬[X]ที่หนึ่งAหรือBจะต้องเป็น supertype ของXหรือXตัวเอง (คิดเกี่ยวกับข้อโต้แย้งของฟังก์ชั่น)

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
}

ฉันใช้เวลาพยายามรวมความคิดนี้กับขอบเขตบนประเภทสมาชิกตามที่เห็นในTLists of harrah / upอย่างไรก็ตามการใช้งานMapกับขอบเขตประเภทได้พิสูจน์แล้วว่าท้าทาย


1
นี่มันยอดเยี่ยมมากขอบคุณ! ฉันลองวิธีก่อนหน้านี้ แต่ยังคงมีปัญหาในการใช้สิ่งนี้กับประเภททั่วไปเป็นส่วนหนึ่งของสหภาพ นี่เป็นการใช้งานเพียงอย่างเดียวที่ฉันสามารถทำงานกับประเภททั่วไปได้
Samer Adra

น่าเศร้า แต่น่าจะเป็นที่คาดหวังเมื่อฉันพยายามใช้วิธี Scala ที่ใช้รูปแบบยูเนี่ยนจากโค้ด Java มันไม่ทำงาน ข้อผิดพลาด: (40, 29) java: method setValue ในการกำหนดค่าคลาสไม่สามารถใช้กับประเภทที่กำหนดได้ ต้องการ: X, scala.Predef. $ less $ colon $ less <UnionTypes.package. $ u00AC <java.lang.Object>, UnionTypes.package. $ u00AC <X>> พบ: เหตุผล java.lang.String: ไม่สามารถอนุมานได้ ประเภทตัวแปร X (รายการอาร์กิวเมนต์ที่เกิดขึ้นจริงและเป็นทางการมีความยาวแตกต่างกัน)
Samer Adra

ยังไม่ชัดเจนเกี่ยวกับรายละเอียดบางอย่างในการใช้งานนี้ ตัวอย่างเช่นบทความต้นฉบับกำหนดการปฏิเสธว่าเป็น "type ¬ [A] = A => ไม่มีอะไร" แต่ในรุ่นนี้ถ้าเพียงแค่มี "ลักษณะนิสัยที่ปิดผนึก¬ [-A]" และลักษณะไม่ได้ขยายทุกที่ มันทำงานอย่างไร
Samer Adra

@Samer Adra มันจะทำงานได้ทั้งสองวิธีบทความใช้Function1เป็นประเภทที่มีอยู่ คุณไม่จำเป็นต้องมีการติดตั้งสิ่งที่คุณต้องมีก็คือหลักฐานของความสอดคล้อง ( <:<)
เจแคร็กเนล

ความคิดวิธีการมีคอนสตรัคเตอร์ที่ยอมรับประเภทยูเนี่ยน?
Samer Adra

13

โซลูชันประเภทคลาสน่าจะเป็นวิธีที่ดีที่สุดในการมาที่นี่โดยใช้ implicits วิธีนี้คล้ายกับวิธี monoid ที่กล่าวถึงในหนังสือ 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 ได้ถามเกี่ยวกับชนิดข้อมูลที่สามารถแสดงถึงชนิดของการรวมกันแล้วทำการวิเคราะห์กรณีที่รันไทม์เพื่อดูว่าชนิดที่แท้จริงกลายเป็นอะไร คลาสประเภทจะไม่สามารถแก้ปัญหานี้ได้เนื่องจากเป็นเวลาที่รวบรวมได้อย่างหมดจด
Tom Crockett

5
จริงคำถามที่ถูกถามคือวิธีการที่จะเปิดเผยพฤติกรรมที่แตกต่างกันสำหรับประเภทที่แตกต่างกัน แต่ไม่มากไป หากไม่มีความรู้เกี่ยวกับคลาสประเภท (และอาจได้รับ C / C ++) ประเภทยูเนี่ยนดูเหมือนจะเป็นทางออกเดียว Eitherประเภทที่มีอยู่ก่อนของสกาล่ามีแนวโน้มที่จะเสริมความเชื่อนี้ การใช้คลาสของประเภทผ่านทาง implicits ของ Scala เป็นวิธีแก้ปัญหาที่ดีกว่า แต่มันเป็นแนวคิดที่ค่อนข้างใหม่และยังไม่เป็นที่รู้จักอย่างกว้างขวางซึ่งเป็นสาเหตุที่ OP ไม่ได้รู้ว่าพวกเขาคิดว่าเป็นทางเลือกที่เป็นไปได้ของสหภาพ
เควินไรท์

สิ่งนี้ใช้กับการพิมพ์ย่อยได้หรือไม่ stackoverflow.com/questions/45255270/…
jhegedus

10

เราต้องการผู้ประกอบการประเภทOr[U,V]ที่สามารถนำมาใช้เพื่อ จำกัด พารามิเตอร์ประเภทXในลักษณะอย่างใดอย่างหนึ่งหรือX <: U X <: 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คอมไพเลอร์สกาล่าให้ระดับที่เรียกว่าU <:< V(และวัตถุโดยนัยของชั้นเรียนที่) และถ้าหากคอมไพเลอร์ Scala สามารถพิสูจน์ได้ว่าเป็นชนิดย่อยของU Vนี่เป็นตัวอย่างที่ง่ายกว่าโดยใช้ข้อ จำกัด ประเภททั่วไปที่ใช้งานได้ในบางกรณี:

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

ตัวอย่างนี้จะทำงานเมื่อXอินสแตนซ์ของชั้นBเป็นStringหรือมีประเภทที่เป็นทั้ง supertype มิได้ชนิดย่อยของหรือB Stringในสองกรณีแรกมันเป็นจริงโดยคำจำกัดความของwithคำนั้น(B with String) <: Bและ(B with String) <: Stringดังนั้นสกาล่าจะให้วัตถุโดยปริยายที่จะถูกส่งผ่านเป็นev: คอมไพเลอร์สกาล่าจะยอมรับอย่างถูกต้องfoo[B]และfoo[String]และ

ในกรณีที่ผ่านมาฉันอาศัยอยู่กับความจริงที่ว่าถ้าU with V <: Xไปแล้วหรือU <: X V <: Xดูเหมือนว่าเป็นความจริงโดยสัญชาตญาณและฉันก็แค่สมมุติมัน มันชัดเจนจากสมมติฐานนี้ว่าทำไมตัวอย่างง่าย ๆ นี้ล้มเหลวเมื่อXเป็น supertype หรือ subtype ของอย่างใดอย่างหนึ่งBหรือString: ตัวอย่างเช่นในตัวอย่างข้างต้นfoo[A]ได้รับการยอมรับอย่างไม่ถูกต้องและfoo[C]ถูกปฏิเสธอย่างไม่ถูกต้อง อีกครั้งสิ่งที่เราต้องการเป็นชนิดของประเภทการแสดงออกเกี่ยวกับตัวแปรบางU, VและXที่เป็นจริงว่าเมื่อหรือX <: UX <: V

ความคิดเรื่องการแตกของสกาล่าสามารถช่วยได้ที่นี่ จำได้trait Inv[-X]ไหม เพราะมันเป็น contravariant ในพารามิเตอร์ชนิดของมันX, และถ้าหากInv[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]หรือInv[V] <: Inv[X]และโดยความหมายของ contravariance นี้เป็นความจริงที่ว่าเมื่อหรือX <: UX <: V

เป็นไปได้ที่จะทำให้สิ่งต่าง ๆ สามารถนำมาใช้ใหม่ได้อีกเพียงเล็กน้อยโดยการประกาศประเภท parametrizable 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]]มากกว่าev) และใช้BOrStringเป็นบริบทชนิดที่ถูกผูกไว้ :

def foo[X : BOrString] = {}

สิ่งที่เราต้องการเป็นวิธีที่ยืดหยุ่นในการสร้างบริบทประเภท บริบทประเภทต้องเป็นประเภท parametrizable และเราต้องการวิธี parametrizable ในการสร้าง ดูเหมือนว่าเรากำลังพยายามฟังก์ชั่นประเภทต่าง ๆ เหมือนที่เราทำกับฟังก์ชั่นเกี่ยวกับค่าต่างๆ กล่าวอีกนัยหนึ่งเราต้องการสิ่งต่อไปนี้:

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]ที่รับประกันว่าจะมีประเภทภายในหนึ่งประเภท นี่คือสัตว์ร้าย ในการให้บริบทบางฟังก์ชั่นdef bar[X <: { type Y = Int }](x : X) = {}จะต้องถูกเรียกด้วย subclasses AnyRefซึ่งมีประเภทที่Yกำหนดไว้ในนั้น:

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

การใช้ตัวดำเนินการปอนด์ช่วยให้เราสามารถอ้างถึงประเภทOr[B, String]#pfส่วนในและการใช้สัญลักษณ์มัดสำหรับตัวดำเนินการชนิดOrเรามาถึงคำนิยามดั้งเดิมของfoo:

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)หนึ่งในประเภทลายเซ็น
Aaron Novstrup

7

คุณอาจจะดูที่MetaScalaOneOfซึ่งมีสิ่งที่เรียกว่า ฉันได้รับความประทับใจว่าสิ่งนี้ไม่ได้ผลดีกับmatchคำสั่ง แต่คุณสามารถจำลองการจับคู่โดยใช้ฟังก์ชันลำดับสูง ตัวอย่างเช่นลองดูตัวอย่างนี้แต่โปรดทราบว่าส่วน "การจับคู่จำลอง" มีการใส่ความคิดเห็นไว้อาจเป็นเพราะมันยังใช้งานไม่ได้

ตอนนี้สำหรับการแก้ไขบางอย่าง: ฉันไม่คิดว่าจะมีอะไรที่เกี่ยวกับการกำหนด Either3, Either4 และอื่น ๆ อีกมากมายตามที่คุณอธิบาย นี่คือสองมาตรฐานที่ 22 ประเภท tuple ที่สร้างขึ้นใน Scala {x, y, z}แน่นอนมันจะดีถ้าสกาล่าได้ในตัวชนิดลักษณะที่แยกออกและบางทีบางไวยากรณ์ที่ดีสำหรับพวกเขาเช่น


6

ฉันคิดว่าประเภท disjoint ชั้นหนึ่งเป็น supertype ที่ถูกปิดผนึกพร้อมด้วยชนิดย่อยอื่นและการแปลงโดยนัยถึง / จากประเภทที่ต้องการของความแตกต่างไปยังประเภทย่อยทางเลือกเหล่านี้

ฉันถือว่าที่อยู่นี้แสดงความคิดเห็นที่ 33 - 36 ของวิธีแก้ปัญหาของ Miles Sabin ดังนั้นประเภทชั้นหนึ่งที่สามารถใช้ในไซต์การใช้งานได้ แต่ฉันไม่ได้ทดสอบ

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

ปัญหาหนึ่งคือสกาล่าจะไม่จ้างในบริบทกรณีการจับคู่แปลงนัยจากIntOfIntOrStringไปInt(และStringOfIntOrStringจะString) ดังนั้นต้องกำหนดสกัดและใช้แทนcase Int(i)case i : Int


เพิ่ม: ฉันตอบสนองต่อ Miles Sabin ที่บล็อกของเขาดังนี้ บางทีอาจมีการปรับปรุงหลายอย่างเช่น:

  1. มันขยายไปมากกว่า 2 ประเภทโดยไม่มีเสียงรบกวนเพิ่มเติมใด ๆ ในการใช้งานหรือไซต์คำจำกัดความ
  2. อาร์กิวเมนต์จะถูกบรรจุไว้ในกล่องโดยปริยายเช่นไม่ต้องการsize(Left(2))หรือsize(Right("test"))หรือ
  3. ไวยากรณ์ของการจับคู่รูปแบบถูกยกเลิกโดยปริยาย
  4. การชกมวยและการ unbox อาจถูกปรับให้เหมาะสมโดยฮอตสปอต JVM
  5. ไวยากรณ์อาจเป็นรูปแบบที่นำไปใช้โดยประเภทสหภาพชั้นหนึ่งในอนาคตดังนั้นการโยกย้ายอาจเป็นไปอย่างราบรื่นหรือไม่ บางทีสำหรับชื่อประเภทยูเนี่ยนมันจะดีกว่าที่จะใช้VแทนOrเช่นIntVString` Int |v| String` ` Int or String` `หรือ` Int|String`ที่ฉันโปรดปราน'?

UPDATE: ปฏิเสธตรรกะของความร้าวฉานสำหรับรูปแบบดังกล่าวข้างต้นต่อไปนี้และผมเพิ่มทางเลือก (และอาจจะมีประโยชน์มากขึ้น) รูปแบบที่บล็อกไมล์ซาบินของ

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)

การอัปเดตอีกครั้ง: เกี่ยวกับความคิดเห็นที่ 23 และ 35 ของวิธีแก้ปัญหาของMile Sabin ต่อไปนี้เป็นวิธีการประกาศประเภทสหภาพที่ไซต์การใช้งาน โปรดทราบว่ามันไม่มีกล่องหลังจากระดับแรกคือมันมีข้อได้เปรียบที่สามารถขยายออกไปได้หลายประเภทในความแตกต่างในขณะที่Eitherความต้องการมวยซ้อนกันและกระบวนทัศน์ในความคิดเห็นก่อนหน้าของฉัน 41 ไม่ขยาย ในคำอื่น ๆD[Int ∨ String]คือการมอบหมาย 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 มีข้อบกพร่องสามประการ

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

เมธอด get ไม่ได้รับการ จำกัด อย่างถูกต้องกับประเภทอินพุตเนื่องจากคอมไพเลอร์จะไม่อนุญาตให้Aอยู่ในตำแหน่ง covariant อาจมีการโต้แย้งว่าเป็นข้อผิดพลาดเพราะสิ่งที่เราต้องการคือหลักฐานเราไม่เคยเข้าถึงหลักฐานในฟังก์ชั่น และฉันได้เลือกที่จะไม่ทดสอบcase _ในgetวิธีดังนั้นฉันจะได้ไม่ต้อง unbox Optionในในmatchsize()


5 มีนาคม 2012: การอัปเดตก่อนหน้าต้องมีการปรับปรุง วิธีแก้ปัญหาของ 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]]
                 ^

ข้อเสนอการอัปเดตก่อนหน้าของฉัน (สำหรับประเภทใกล้ระดับเฟิร์สคลาสใกล้) ทำให้การพิมพ์ย่อยล้มเหลว

 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ปรากฏขึ้นทั้งใน covariant (ชนิดกลับ) และ contravariant (input ฟังก์ชั่นหรือในกรณีนี้ค่าตอบแทนของฟังก์ชั่นซึ่งเป็นฟังก์ชั่นการป้อนข้อมูล) ตำแหน่งจึงสามารถแทนเพียง แต่จะคงที่

ทราบว่าA => Nothingเป็นสิ่งจำเป็นเท่านั้นเพราะเราต้องการAอยู่ในตำแหน่ง contravariant เพื่อให้ supertypes ของA ไม่ได้ชนิดย่อยของD[¬[A]]มิได้D[¬[A] with ¬[U]]( ดูเพิ่มเติมที่ ) เนื่องจากเราจะต้อง contravariance คู่เราสามารถบรรลุเทียบเท่ากับการแก้ปัญหาไมล์แม้ว่าเราสามารถทิ้งและ¬

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

สังเกตข้อบกพร่อง 2 ตัวก่อนหน้าใน Scala ยังคงอยู่ แต่ตัวที่ 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

ฉันได้รับการคิดว่าชนิดแยกชั้นแรกมีความสำคัญมากทั้งสำหรับเหตุผลที่ประเทศศรีลังกามีพวกเขาและเพราะแทนที่จะsubsumingไปAnyซึ่งหมายความว่า unboxing กับmatchประเภทคาดว่าสามารถสร้างข้อผิดพลาด runtime ที่แกะกล่องของ ( คอลเลกชันที่แตกต่างกันมี ก) สามารถแยกประเภทการตรวจสอบได้ (Scala ต้องแก้ไขข้อบกพร่องที่ฉันบันทึกไว้) สหภาพแรงงานนั้นตรงไปตรงมามากกว่าความซับซ้อนของการใช้HList แบบทดลองของMetascalaสำหรับคอลเลกชันที่ต่างกัน


# 3 รายการข้างต้นเป็นไม่ได้เป็นข้อผิดพลาดในคอมไพเลอร์สกาลา หมายเหตุฉันเดิมไม่ได้นับว่าเป็นข้อผิดพลาดจากนั้นทำการแก้ไขอย่างไม่ระมัดระวังในวันนี้และทำเช่นนั้น (ลืมเหตุผลเดิมของฉันโดยไม่ระบุว่าเป็นข้อบกพร่อง) ฉันไม่ได้แก้ไขโพสต์อีกครั้งเพราะฉันอยู่ที่ขีด จำกัด การแก้ไข 7 ครั้ง
Shelby Moore III

ข้อผิดพลาด # 1 ข้างต้นสามารถหลีกเลี่ยงได้ด้วยสูตรที่แตกต่างกันของsizeฟังก์ชั่น
Shelby Moore III 3

รายการ # 2 ไม่ใช่ข้อผิดพลาด Scala ไม่สามารถแสดงประเภทสหภาพแรงงาน เอกสารที่เชื่อมโยงจะให้รหัสอีกเวอร์ชันหนึ่งเพื่อsizeไม่ให้รับD[Any]เป็นอินพุตอีกต่อไป
Shelby Moore III

ฉันไม่ค่อยได้รับคำตอบนี้เป็นคำตอบสำหรับคำถามนี้: stackoverflow.com/questions/45255270//
jhegedus

5

มีวิธีอื่นที่เข้าใจได้ง่ายขึ้นเล็กน้อยหากคุณไม่รีบไปหา Curry-Howard:

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

นั่นคือทั้งหมดที่ฉลาดมาก แต่ฉันค่อนข้างแน่ใจว่าคุณรู้อยู่แล้วว่าคำตอบสำหรับคำถามนำของคุณมีหลากหลาย "ไม่" สกาล่าจัดการกับการบรรทุกเกินพิกัดที่แตกต่างกันและต้องยอมรับว่าค่อนข้างหรูหรากว่าที่คุณอธิบาย บางส่วนเกิดจากการทำงานร่วมกันของ Java บางส่วนเกิดจากการไม่ต้องการที่จะตีเคสที่มีขอบของอัลกอริทึมการอนุมานประเภทและบางส่วนนั้นเป็นเพราะมันไม่ได้เป็น Haskell


5
ในขณะที่ฉันใช้ Scala อยู่พักหนึ่งฉันก็ไม่เป็นคนที่มีความรู้และฉลาดเท่าที่คุณคิด ในตัวอย่างนี้ฉันสามารถดูว่าห้องสมุดสามารถให้บริการโซลูชั่นได้อย่างไร มันสมเหตุสมผลแล้วที่จะสงสัยว่าห้องสมุดนั้นมีอยู่จริงหรือไม่
Aaron Novstrup

1

การเพิ่มคำตอบที่ยอดเยี่ยมแล้วที่นี่ นี่คือส่วนสำคัญที่สร้างขึ้นตามประเภทสหภาพ Miles Sabin (และความคิดของ Josh) แต่ยังทำให้พวกเขากำหนดไว้ซ้ำดังนั้นคุณสามารถมี> 2 ประเภทในสหภาพ ( def foo[A : UNil Or Int Or String Or List[String])

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

NB: ฉันควรเพิ่มว่าหลังจากเล่นกับข้างต้นสำหรับโครงการฉันจบลงด้วยการกลับไปที่ประเภทผลรวมเก่า (เช่นลักษณะที่ปิดผนึกด้วยคลาสย่อย) ประเภทสหภาพแบบรวมไมล์ของ Sabin นั้นยอดเยี่ยมสำหรับการ จำกัด พารามิเตอร์ประเภท แต่ถ้าคุณต้องการส่งคืนชนิดแบบรวมจะไม่ให้อะไรมาก


สิ่งนี้สามารถแก้ปัญหาการA|C <: A|B|Cพิมพ์ย่อยได้หรือไม่ stackoverflow.com/questions/45255270/… ไส้ในของฉันรู้สึกไม่เพราะหมายความว่าA or Cมันจะต้องเป็นประเภทย่อย(A or B) or Cแต่ที่ไม่มีประเภทA or Cนั้นจึงไม่มีความหวังในการสร้างA or Cประเภทย่อยA or B or Cด้วยการเข้ารหัสนี้อย่างน้อย .. . คุณคิดอย่างไร ?
jhegedus

0

จากเอกสารด้วยการเพิ่ม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.