예를 들어 (공동, 대조 및 in) 분산이 어떻게 작동합니까?


147

이 질문 에 이어 누군가 Scala에서 다음을 설명 할 수 있습니다.

class Slot[+T] (var some: T) { 
   //  DOES NOT COMPILE 
   //  "COVARIANT parameter in CONTRAVARIANT position"

}

형식 선언 +TT형식 선언 의 차이점을 이해합니다 (을 사용하면 컴파일됩니다 T). 하지만 어떻게 하나 실제로 일을 만들기에 의지하지 않고 형식 매개 변수의 공변 인 클래스 쓰기 않습니다 unparametrized를 ? 의 인스턴스로만 다음을 만들 수 있는지 어떻게 확인할 수 T있습니까?

class Slot[+T] (var some: Object){    
  def get() = { some.asInstanceOf[T] }
}

편집 -이제 이것을 아래로 가져 왔습니다.

abstract class _Slot[+T, V <: T] (var some: V) {
    def getT() = { some }
}

이것은 모두 좋지만 이제는 두 가지 유형의 매개 변수가 있습니다. 다음과 같이 질문을 다시하겠습니다.

유형 이 공변 인불변 Slot 클래스를 작성하려면 어떻게 해야합니까?

편집 2 : 어! 내가 사용 var하지 val. 다음은 내가 원하는 것입니다.

class Slot[+T] (val some: T) { 
}

6
때문에 var동안 설정 가능 val하지 않습니다. 스칼라의 불변 컬렉션이 공변량이지만 변이 가능한 컬렉션이 아닌 이유도 마찬가지입니다.
oxbow_lakes

이것은 이러한 맥락에서 흥미로운 일이 될 수 있습니다 scala-lang.org/old/node/129
user573215

답변:


302

일반적으로 공변량 유형 매개 변수는 클래스가 하위 유형화됨에 따라 범위가 변경 될 수있는 유형입니다 (또는 하위 유형에 따라 달라 지므로 "co-"접두어). 보다 구체적으로 :

trait List[+A]

List[Int]의 하위 유형 List[AnyVal]이기 때문에 Int의 하위 유형입니다 AnyVal. 이는 List[Int]유형 값 List[AnyVal]이 예상 되는 인스턴스를 제공 할 수 있음을 의미합니다 . 이것은 제네릭이 작동하는 데 매우 직관적 인 방법이지만 변경 가능한 데이터가있을 때 소리가 나지 않습니다 (유형 시스템이 깨짐). 이것이 제네릭이 Java에서 변하지 않는 이유입니다. Java 배열을 사용하는 소리가 들리지 않는 간단한 예 (공변이 잘못됨) :

Object[] arr = new Integer[1];
arr[0] = "Hello, there!";

우리는 방금 유형의 값을 할당했습니다. String 의 배열에Integer[] . 명백한 이유 때문에 이것은 나쁜 소식입니다. Java 타입 시스템은 실제로 컴파일 타임에 이것을 허용합니다. JVM은 "유용하게" ArrayStoreException런타임에 던질 것이다 . 스칼라의 타입 시스템은 Array클래스 의 타입 매개 변수 가 변하지 않기 때문에이 문제를 방지합니다 (선언이 [A]아닌 [+A]).

반공 분산으로 알려진 다른 유형의 분산이 있습니다. . 공분산으로 인해 일부 문제가 발생할 수있는 이유를 설명하므로 매우 중요합니다. 공분산은 말 그대로 공분산의 반대입니다. 매개 변수 는 하위 유형에 따라 위쪽 으로 다양 합니다. 매우 직관적 인 응용 프로그램이 있기는하지만 반 직관적이기 때문에 부분적으로 덜 일반적입니다.

trait Function1[-P, +R] {
  def apply(p: P): R
}

type 매개 변수 에 " - "분산 주석이 P있습니다. Function1전체적으로이 선언 은P 공변량 및 공변량을R . 따라서 다음과 같은 공리를 도출 할 수 있습니다.

T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']

공지 사항 T1'의 하위 유형 (또는 같은 종류)해야합니다T1 그것이 대한 반대 인 반면에, T2그리고 T2'. 영어로 다음과 같이 읽을 수 있습니다.

함수 A는 다른 기능의 하위 유형 B 의 파라미터를 입력하면 , A는 의 파라미터 형태의 슈퍼이고 B가 의 반환 입력 중 A는 의 리턴 타입의 서브 타입 인 B .

이 규칙의 이유는 독자에게 연습으로 남겨두고 있습니다 (힌트 : 위의 배열 예제와 같이 함수가 하위 유형으로 지정되므로 다른 경우에 대해 생각하십시오).

공분산 및 반공 분산에 대한 새로운 지식을 통해 다음 예제가 컴파일되지 않는 이유를 확인할 수 있습니다.

trait List[+A] {
  def cons(hd: A): List[A]
}

문제는 A공변량이지만 cons함수는 유형 매개 변수가 변하지 않을 것으로 예상합니다. . 따라서 A잘못된 방향으로 변화하고 있습니다. 흥미롭게도, 우리는에 List반 변형 을 만들어서이 문제를 해결할 수는 A있지만 List[A], cons함수가 리턴 타입이 공변 인 을 기대하기 때문에 리턴 타입 은 유효하지 않습니다 .

여기서 우리의 유일한 두 가지 옵션은 a) A불변을 만들고 공분산의 훌륭하고 직관적 인 하위 입력 속성을 잃거나 b) 하한으로 cons정의 A되는 메서드에 지역 유형 매개 변수를 추가하는 것입니다 .

def cons[B >: A](v: B): List[B]

이제 유효합니다. 당신은 그것을 상상할 수 있습니다A 아래로B 수 있지만,AA 그것의 하한 이기 때문에 . 이 메소드 선언으로A 공변량을 수 있으며 모든 것이 잘 작동합니다.

이 트릭 List은 덜 구체적인 유형에 특화된 인스턴스를 반환하는 경우에만 작동합니다 B. List변경 가능 하게 만들 려고하면 type 값을 type B변수에 할당하려고 시도하기 때문에 문제 A가 발생합니다. 이는 컴파일러에서 허용하지 않습니다. 변경 가능성이있을 때마다 (종종 접근 자와 함께) 불변성을 암시하는 특정 유형의 메소드 매개 변수가 필요한 일종의 뮤 테이터가 필요합니다. 공분산은 변경 불가능한 데이터와 함께 작동합니다. 가능한 유일한 조작은 접근 자이므로 공변량 리턴 유형이 제공 될 수 있습니다.


4
이것을 평범한 영어로 표현할 수 있습니까? 매개 변수로 더 간단한 것을 취할 수 있고 더 복잡한 것을 반환 할 수 있습니까?
Phil

1
Java 컴파일러 (1.7.0)가 "Object [] arr = new int [1];"을 컴파일하지 않습니다. "java : 호환되지 않는 유형이 필요합니다. java.lang.Object [] found : int []"오류 메시지가 표시됩니다. "Object [] arr = new Integer [1];"을 의미한다고 생각합니다.
Emre Sevinç

2
"이 규칙의 이유는 독자에게 연습으로 남아 있습니다 (힌트 : 위의 배열 예제와 같이 함수가 하위 유형화됨에 따라 다른 경우에 대해 생각하십시오"). 실제로 몇 가지 예를 들어 주시겠습니까?
perryzheng

2
당 @perryzheng 걸릴 trait Animal, trait Cow extends Animal, def iNeedACowHerder(herder: Cow => Unit, c: Cow) = herder(c)def iNeedAnAnimalHerder(herder: Animal => Unit, a: Animal) = herder(a). 그러면 iNeedACowHerder({ a: Animal => println("I can herd any animal, including cows") }, new Cow {})가축 축산업이 젖소를 모을 수 있기 때문에 괜찮습니다. 그러나 가축 축산업이 iNeedAnAnimalHerder({ c: Cow => println("I can herd only cows, not any animal") }, new Animal {})모든 동물을 모을 수 없기 때문에 컴파일 오류가 발생합니다.
Lasf

이것은 관련되어 있고 분산에 도움이되었습니다 : typelevel.org/blog/2016/02/04/variance-and-functors.html
Peter Schmitz

27

@Daniel은 그것을 잘 설명했습니다. 그러나 허용 된 경우 간단히 설명하십시오.

  class Slot[+T](var some: T) {
    def get: T = some   
  }

  val slot: Slot[Dog] = new Slot[Dog](new Dog)   
  val slot2: Slot[Animal] = slot  //because of co-variance 
  slot2.some = new Animal   //legal as some is a var
  slot.get ??

slot.get그런 다음에 변환에 실패하여 런타임에 오류가 발생 Animal합니다.Dog (duh!) 합니다.

일반적으로 변이성은 공분산 및 반 분산과 잘 맞지 않습니다. 이것이 모든 Java 콜렉션이 변하지 않는 이유입니다.


7

이에 대한 자세한 설명은 57 페이지 스칼라를 참조하십시오 .

귀하의 의견을 올바르게 이해하고 있다면 56 페이지 하단에서 시작하는 구절을 다시 읽어야합니다 (기본적으로, 당신이 요구하는 것은 런타임 검사없이 유형 안전하지 않다는 것입니다. 그래서 당신은 운이 없어요). 구문을 사용하기 위해 예제를 번역 :

val x = new Slot[String]("test") // Make a slot
val y: Slot[Any] = x             // Ok, 'cause String is a subtype of Any
y.set(new Rational(1, 2))        // Works, but now x.get() will blow up 

귀하의 질문을 이해하지 못한다고 생각되는 경우 (고유 한 가능성), 문제 설명에 설명 / 문맥을 추가해보십시오. 다시 시도하겠습니다.

편집에 대한 응답으로 : 불변 슬롯은 완전히 다른 상황입니다 ... * smile * 위의 예가 도움이 되었기를 바랍니다.


나는 그것을 읽었다; 불행히도 나는 (여전히) 내가 위에서 무엇을 할 수 있는지 이해하지 못한다 (즉, 실제로 T로 매개 변수화 된 클래스 공변량을 작성)
oxbow_lakes

나는 이것이 약간 가혹하다는 것을 깨달았을 때 다운 마크를 제거했습니다. 나는 예를 들어 스칼라에서 비트를 읽었다는 질문을 분명히 했어야했다. 방금 "형식적이지 않은"방식으로 설명하고 싶었습니다
oxbow_lakes

@oxbow_lakes smile 스칼라를 두려워합니다 예 들어 덜 공식적인 설명입니다. 기껏해야 구체적인 예를 사용하여 여기서 작동 할 수 있습니다.
MarkusQ

죄송합니다. 슬롯을 변경하지 않으려 고합니다. 난 그냥 문제가 내가 VAR와 val되지 선언임을 깨달았다
oxbow_lakes

3

매개 변수에 하한을 적용해야합니다. 구문을 기억하는 데 어려움을 겪고 있지만 다음과 같이 보일 것입니다.

class Slot[+T, V <: T](var some: V) {
  //blah
}

스칼라 예제는 이해하기 약간 어렵지만 몇 가지 구체적인 예제가 도움이 될 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.