“유형 분리 (union types)”를 정의하는 방법은 무엇입니까?


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같은 목적을 달성하기 위해 대체 솔루션, ....합니까 누구의 노하우를? 내가 알기로는 스칼라는 내장 된 "유형 분리"를 가지고 있지 않다. 또한 위에서 정의한 암시 적 변환이 표준 라이브러리에 숨겨져 있으므로 가져올 수 있습니까?

답변:


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되어 있습니다. 그런 다음 감시 객체를와 동일한 소스에 정의해야합니다 StringOrInt.
랜달 슐츠

3
이 솔루션을 다소 일반화하려고했습니다 (아래 답변으로 게시). Either접근 방식 과 비교하여 주요 단점 은 일치를 확인하기 위해 많은 컴파일러 지원을 잃는 것 같습니다.
Aaron Novstrup

좋은 트릭! 그러나 봉인 클래스를 사용하더라도 foo 범위 내에서 암시 적 val b = new StringOrInt [Boolean]을 정의하거나 명시 적으로 foo (2.9) (new StringOrInt [Double])를 호출하여 클라이언트 코드에서이를 피할 수 있습니다. 수업을 추상적으로 만들어야한다고 생각합니다.
파올로 팔 라벨

2
예; 아마 사용하는 것이 좋을 것입니다trait StringOrInt ...
Mechanical snail

7
시는 당신은 단순히 변경 지원 아형에 원하는 경우 StringOrInt[T]StringOrInt[-T](참조 stackoverflow.com/questions/24387701/...를 )
에 란 메단

178

Miles Sabin 은 Curry-Howard isomorphism을 통해 Scala의 최근 블로그 게시물 Unboxed union 유형에서 Union 유형 을 얻는 매우 좋은 방법을 설명합니다 .

그는 먼저 유형의 부정을 다음과 같이 정의합니다.

type ¬[A] = A => Nothing

De Morgan의 법칙을 사용하여 노조 유형을 정의 할 수 있습니다.

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


6
위의 의견은 독자적인 답변이어야합니다. 그것은 단지 Miles의 아이디어를 구현 한 것이지만 Maven Central의 패키지에 멋지게 싸여 있으며 (?) 유니 코드 기호가 없으면 빌드 프로세스에서 무언가 문제가 될 수 있습니다.
Jim Pivarski

2
그 재미있는 캐릭터는 부울 부정 입니다.
michid

1
처음에는 아이디어가 너무 복잡해 보였습니다. 이 스레드에서 언급 된 거의 모든 링크를 읽으면서 아이디어와 구현의 아름다움에 사로 잡혔습니다 :-) ...하지만 여전히 이것이 복잡하다고 생각합니다 ... 지금은 아직 똑바로 사용할 수 없기 때문에 스칼라에서 멀어 지십시오. Miles가 말한 것처럼 : "이제 Martin과 Adriaan을 직접 연결하기 만하면됩니다."
Richard Gomes

44

새로운 실험적인 Scala 컴파일러 인 Dotty 는 공용체 유형 (written 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

다음은 Union 유형을 인코딩하는 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 의이 훌륭한 블로그 게시물 아래 스칼라에서 조합 유형을 인코딩하는 다른 방법을 제공합니다.


6
불행히도이 인코딩은 scala> f(9.2: AnyVal)무시할 수 있습니다 . typechecker를 전달합니다.
Kipton Barros

@ Kitton : 슬프다. Miles Sabin의 인코딩에도이 문제가 있습니까?
missingfaktor

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

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이 지적했듯이 혼합 유형의 컬렉션 / Vararg는 처리하지 않습니다.
  • 일치하는 것이 완전하지 않으면 컴파일러에서 경고를 발행하지 않습니다.
  • 일치하는 경우에 포함되지 않은 경우 컴파일러에서 오류를 발행하지 않습니다
  • 등의 Either접근 방식, 더 일반화 유사한 정의 요구 Or3, Or4등 특징. 물론 이러한 특성을 정의하는 것이 해당 Either클래스를 정의하는 것보다 훨씬 간단 합니다.

최신 정보:

미치 블레 빈스는 보여 매우 유사한 방식 그것에게 "말더듬 또는"더빙, 이상의 두 가지 유형으로 일반화하는 방법과 쇼를.


18

나는 다른 사람들이 언급 한이 지역에서마일 목록 Sabin의 작업 단순화와 유형 목록 개념을 결합하여 비교적 깨끗한 n-ary 조합 유형을 우연히 발견했습니다 .

¬[-A]반 변하는 유형 이 주어지면 A, 정의 에 따라 유형의 순서를 뒤집어 A <: B쓸 수 ¬[B] <: ¬[A]있습니다.

주어진 타입 A, B그리고 X우리는 표현하고 싶다 X <: A || X <: B. contravariance을 적용, 우리가 얻을 ¬[A] <: ¬[X] || ¬[B] <: ¬[X]. 차례로이 캔은 다음과 같이 표현 될 수 ¬[A] with ¬[B] <: ¬[X]있는 하나 A또는 B의 슈퍼해야합니다 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
}

harrah / up 에서 볼 수 있듯이이 아이디어를 멤버 유형의 상한과 결합하려고 노력하는 데 시간 TList보냈지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

이 구현의 일부 세부 사항에 대해서는 아직 명확하지 않습니다. 예를 들어, 원래 기사에서는 부정을 "type ¬ [A] = A => Nothing"으로 정의했지만이 버전에서는 "밀봉 된 특성 ¬ [-A]"만 있고 특성이 어느 곳으로 확장되지 않은 경우이 버전에서 부정을 정의했습니다. 어떻게 작동합니까?
Samer Adra

@Samer Adra 어느 쪽이든 작동하지만 기사는 Function1기존의 반 변형 유형으로 사용 됩니다. 구현이 필요하지 않으며 준수 증거 ( <:<) 만 있으면 됩니다.
J Cracknell

유니온 유형을 허용하는 생성자를 갖는 방법에 대한 아이디어가 있습니까?
Samer Adra

13

타입 클래스 솔루션은 암시 적을 사용하는 가장 좋은 방법 일 것입니다. 이것은 Odersky / Spoon / Venners 책에 언급 된 monoid 접근법과 유사합니다 :

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유형은 이러한 신념을 강화시키는 경향이 있습니다. 스칼라의 암시 적을 통해 유형 클래스를 사용하는 것이 근본적인 문제에 대한 더 나은 솔루션이지만 비교적 새로운 개념이며 여전히 널리 알려져 있지 않기 때문에 OP는 이러한 유형을 가능한 유형으로 대체 유형으로 생각하지 못했습니다.
Kevin Wright

이 작업은 하위 입력과 함께 작동합니까? stackoverflow.com/questions/45255270/…
jhegedus

10

또는 Or[U,V]형식 매개 변수를 제한하는 데 사용할 수 있는 형식 연산자 가 X필요합니다 . 우리가 얻을 수있는 한 가깝게 나오는 정의는 다음과 같습니다.X <: 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!

이것은 몇 가지 스칼라 타입 트릭을 사용합니다. 주요한 것은 일반화 된 타입 제약 의 사용이다 . 유형을 감안 U하고 V, 스칼라 컴파일러라는 클래스 제공 U <:< V스칼라 컴파일러는 증명할 수있는 경우에만 해당이 경우 (그 클래스의 내장 객체) U의 하위 유형입니다 V. 다음은 일부 경우에 작동하는 일반 형식 제약 조건을 사용하는 간단한 예입니다.

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

이 예제는 Xclass B, a 의 인스턴스가 String또는 수퍼 타입이 아니 B거나 또는 서브 타입이 아닌 유형을 가질 때 작동합니다 String. 처음 두 경우에,이 정의에 의해 사실 with키워드 (B with String) <: B(B with String) <: String스칼라로 전달됩니다 암시 적 객체를 제공 할 것입니다, 그래서 ev: 스칼라 컴파일러가 제대로 받아 foo[B]foo[String] .

마지막 경우에는 if U with V <: X, then U <: X또는 이라는 사실에 의존합니다 V <: X. 그것은 직관적으로 사실이며, 나는 단순히 그것을 가정하고 있습니다. 때이 간단한 예제가 실패 이유는이 가정에서 분명 X의 슈퍼 또는 하위 유형이 하나 B또는 String예를 들어, 위의 예에서, foo[A]잘못 인정하고 foo[C]잘못 거부됩니다. 다시 말하지만, 우리가 원하는 것은 변수에 타입 표현의 일종이다 U, V그리고 X그 정확히 해당하는 경우 X <: U또는X <: 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 <: U이나 X <: V.

매개 변수화 가능한 유형을 선언하고 BOrString[X]다음과 같이 사용하여 물건을 좀 더 재사용 가능하게 만들 수 있습니다.

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

스칼라는 이제 유형을 구성하려고 시도 BOrString[X]마다에 대한 Xfoo호출 될 때, 그리고 유형을 정확하게 구축 할 것 X중 하나의 하위 유형이다 B또는 String. 그것은 효과가 있으며 속기 표기법이 있습니다. 아래 구문은 동등 ev하며 (이제 메소드 본문에서 implicitly[BOrString[X]]단순히 참조하기보다는 참조해야 함 제외 ev) 유형 컨텍스트 바인딩BOrString 으로 사용 됩니다 .

def foo[X : BOrString] = {}

우리가 정말로 원하는 것은 타입 컨텍스트 바운드를 생성하는 유연한 방법입니다. 유형 컨텍스트는 매개 변수화 할 수있는 유형이어야하며 매개 변수화 가능한 방법으로 작성해야합니다. 값에 함수를 카레하는 것처럼 유형에 함수를 카레하려고하는 것처럼 들립니다. 다시 말해, 우리는 다음과 같은 것을 원합니다 :

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

그것은 스칼라에서 직접 가능 하지는 않지만 꽤 가까워지는 데 사용할 수있는 트릭이 있습니다. 그것은 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) = {}가 정의 된 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). 유형 서명 중 하나에 추가하십시오 .
Aaron Novstrup

7

당신은 한 번 봐 걸릴 수 있습니다 MetaScala 이라는 것을 가지고,OneOf . 나는 이것이 match문장에서 잘 작동하지 않지만 고차 함수를 사용하여 일치를 시뮬레이션 할 수 있다는 인상을 얻습니다 . 예를 들어, 이 코드 조각을 살펴보십시오. "시뮬레이션 된 일치"부분은 아직 작동하지 않았기 때문에 주석 처리되어 있습니다.

이제 편집을 위해 : 당신이 묘사 한대로 Either3, Either4 등을 정의하는 것에 대해 거칠게 생각하지 않습니다. 이것은 본질적으로 스칼라에 내장 된 표준 22 튜플 유형에 이중입니다. 스칼라에 내장 형 결절 유형이 있다면 좋을 것 {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

한 가지 문제는 스칼라의 경우 일치하는 맥락에서하지 고용에서 암시 적 변환 것이다 IntOfIntOrStringInt(그리고 StringOfIntOrString로는 String), 그래서 추출기를 정의하고 사용해야합니다 case Int(i)대신 case i : Int.


ADD : 다음과 같이 블로그에서 Miles Sabin에 응답했습니다. 아마도 몇 가지 개선 사항이 있습니다.

  1. 사용 또는 정의 사이트에서 추가 노이즈없이 2 가지 이상의 유형으로 확장됩니다.
  2. 인수는 암시 적으로 상자에 표시됩니다 (예 : size(Left(2))또는 필요 없음) size(Right("test")).
  3. 패턴 일치 구문은 암시 적으로 언 박싱됩니다.
  4. JVM 핫스팟에 의해 boxing 및 unboxing이 최적화 될 수 있습니다.
  5. 구문은 미래의 일류 공용체 유형에서 채택 된 것일 수 있으므로 마이그레이션이 원활 할 수 있습니까? 아마도 공용체 타입 이름 VOr경우 IntVString,` Int |v| String`,` Int or String`또는 내가 좋아하는` Int|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 에 할당 가능합니다 (즉, 하위 유형입니다) 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]])
                ^

분명히 스칼라 컴파일러에는 세 가지 버그가 있습니다.

  1. 대상 분리에서 첫 번째 유형 이후의 모든 유형에 대해 올바른 암시 적 기능을 선택하지 않습니다.
  2. D[¬[Double]]경기 에서 사건을 배제하지는 않습니다 .

삼.

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했기 때문에 상자를 풀지 않아도됩니다.Option 에서을 matchsize().


2012 년 3 월 5 일 : 이전 업데이트에는 개선이 필요합니다. 마일즈 사빈의 솔루션 은 서브 타이핑에서 올바르게 작동했습니다.

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 (return type)와 contravariant (function input 또는이 경우 function input 인 function의 return value) 위치 나타나기 때문에 치환은 변하지 않을 수 있다는 것입니다.

A => Nothing우리가 원하는 때문 필요 Acontravariant 위치에, 그래서의 슈퍼 타입이 있다고 A 하지 아류D[¬[A]]D[¬[A] with ¬[U]]( 도 참조 ). 이중 반공 분산 만 필요하기 때문에 ¬and를 폐기 할 수 있어도 Miles 솔루션과 동등한 성능을 달성 할 수 있습니다 .

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 개의 버그가 남아 있지만, 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

나는 일류 교차 유형이 매우 중요하다고 생각하고 한 모두 실론를 가지고 이유를 대신하기 때문에, 그리고 subsumingAny로모그래퍼 개봉기하는 수단 match런타임 오류를 생성 할 수 있습니다 예상 유형에, (의 개봉기 이기종 컬렉션에 포함 a) 분리는 유형을 검사 할 수 있습니다 (Scala는 내가 언급 한 버그를 수정해야합니다). 이종 수집 에 메타 스칼라 의 실험적 HList사용 하는 것보다 복잡한 것이 더 간단합니다 .


위의 # 3 항목 은 Scala 컴파일러의 버그아닙니다 . 참고 나는 원래 버그로 번호를 매기 지 않았고 오늘 부주의하게 편집하여 그렇게했습니다 (버그를 언급하지 않은 원래의 이유를 잊어 버렸습니다). 7 개의 편집 한도에 도달하여 게시물을 다시 편집하지 않았습니다.
쉘비 무어 III

위의 # 1 버그 size함수다른 공식 으로 피할 수 있습니다 .
쉘비 무어 III

# 2 아이템은 버그아닙니다. 스칼라는 조합 유형을 완전히 표현할 수 없습니다 . 링크 된 문서는 다른 버전의 코드를 제공하므로 size더 이상 D[Any]입력으로 허용되지 않습니다 .
쉘비 무어 III

나는이 답변을 얻지 못했습니다, 이것은 또한이 질문에 대한 답변
입니까

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
스칼라를 한동안 사용해 왔지만 생각보다 똑똑하지도 않고 똑똑하지도 않습니다. 이 예제에서 라이브러리가 솔루션을 제공하는 방법을 볼 수 있습니다. 그런 다음 그러한 라이브러리가 존재하는지 (또는 대안이 있는지) 궁금해하는 것이 합리적입니다.
Aaron Novstrup

1

이미 훌륭한 답변을 여기에 추가하십시오. 다음은 Miles Sabin Union 유형 (및 Josh의 아이디어)을 기반으로하지만 재귀 적으로 정의되도록하는 요지입니다. 따라서 Union에서 2 가지 이상의 유형을 가질 수 있습니다 ( def foo[A : UNil Or Int Or String Or List[String])

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

NB : 위와 같이 프로젝트를 진행 한 후에는 일반 구식 유형 (즉, 서브 클래스가있는 봉인 된 특성)으로 돌아가는 것을 덧붙여 야합니다. Miles Sabin Union 유형은 유형 매개 변수를 제한하는 데 유용하지만 Union 유형을 반환 해야하는 경우 많이 제공하지 않습니다.


이것으로 A|C <: A|B|C서브 타이핑 문제를 해결할 수 있습니까 ? stackoverflow.com/questions/45255270 / ... 내 직감이 NO라고 생각하기 때문에 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.