나는 스칼라 여행 : 추상 유형 을 읽고있었습니다 . 언제 추상 유형을 사용하는 것이 더 낫습니까?
예를 들어
abstract class Buffer {
type T
val element: T
}
예를 들어
abstract class Buffer[T] {
val element: T
}
나는 스칼라 여행 : 추상 유형 을 읽고있었습니다 . 언제 추상 유형을 사용하는 것이 더 낫습니까?
예를 들어
abstract class Buffer {
type T
val element: T
}
예를 들어
abstract class Buffer[T] {
val element: T
}
답변:
이 문제에 대한 좋은 견해는 다음과 같습니다.
Bill Venners와 Frank Sommers의
Part III, Martin Odersky와의 스칼라 타입 시스템 A 대화 의 목적
(2009 년 5 월 18 일)
업데이트 (2009 년 10 월) : 아래의 다음 무엇을 실제로 빌 베너 스에 의해이 새로운 기사에서 설명하고있다 :
스칼라에서 제네릭 형식 매개 변수 대 추상 형식 회원 (마지막에 요약 참조)
(2009 년 5 월, 첫 번째 인터뷰의 관련 내용은 다음과 같습니다.)
항상 두 가지 추상화 개념이있었습니다.
Java에는 둘 다 있지만 추상적 인 내용에 따라 다릅니다.
Java에는 추상 메소드가 있지만 메소드를 매개 변수로 전달할 수 없습니다.
추상 필드는 없지만 값을 매개 변수로 전달할 수 있습니다.
마찬가지로 추상 형식 멤버는 없지만 형식을 매개 변수로 지정할 수 있습니다.
Java에는 세 가지가 모두 있지만, 어떤 종류의 것들에 어떤 추상화 원칙을 사용할 수 있는지에 대한 구별이 있습니다. 그리고 당신은이 구별이 상당히 임의적이라고 주장 할 수 있습니다.
우리는 세 종류의 구성원 모두에 대해 동일한 구성 원칙을 갖기로 결정했습니다 .
따라서 값 필드뿐만 아니라 추상 필드도 가질 수 있습니다.
메소드 (또는 "함수")를 매개 변수로 전달하거나 메소드를 추상화 할 수 있습니다.
유형을 매개 변수로 지정하거나 해당 유형을 추상화 할 수 있습니다.
우리가 개념적으로 얻는 것은 하나를 다른 하나의 관점에서 모델링 할 수 있다는 것입니다. 적어도 원칙적으로 모든 종류의 매개 변수화를 객체 지향 추상화 형태로 표현할 수 있습니다. 어떤 의미에서 스칼라는 더 직교적이고 완전한 언어라고 말할 수 있습니다.
특히 추상 유형이 당신을 구입하는 것은 이전에 이야기했던 이러한 공분산 문제에 대한 훌륭한 치료법입니다 .
오랫동안 존재해온 하나의 표준 문제는 동물과 음식의 문제입니다.
퍼즐은 몇 가지 음식을 먹는 Animal
방법 으로 수업 을하는 것이 었습니다 eat
.
문제는 우리가 Animal을 서브 클래 싱하고 Cow와 같은 클래스를 가지고 있다면, 그들은 임의의 음식이 아닌 잔디 만 먹을 것입니다. 예를 들어 소는 물고기를 먹을 수 없었습니다.
당신이 원하는 것은 암소가 잔디 만 먹고 다른 것은 먹지 않는 먹는 방법을 가지고 있다고 말할 수 있다는 것입니다.
사실, Java에서 그렇게 할 수는 없습니다. 왜냐하면 앞에서 이야기했던 Apple 변수에 과일을 할당하는 문제와 같이 건전하지 않은 상황을 만들 수 있기 때문입니다.
답은 추상적 인 타입을 Animal 클래스에 추가 한다는 것 입니다.
내 새 Animal 클래스의 유형은 SuitableFood
알 수 없습니다.
추상적 인 타입입니다. 유형의 구현을 제공하지 않습니다. 그럼 당신은 eat
먹는 방법이 SuitableFood
있습니다.
그리고 다음에 Cow
클래스 I는 OK, I 클래스 확장하는 암소가, 말을 Animal
하고, 위해를 Cow type SuitableFood equals Grass
.
따라서 추상 유형은 내가 모르는 수퍼 클래스에서 이러한 유형의 개념을 제공하며, 나중에 내가 아는 것으로 서브 클래스에서 채 웁니다 .
실제로 당신은 할 수 있습니다. 먹는 음식의 종류를 동물 클래스에 매개 변수로 지정할 수 있습니다.
그러나 실제로 많은 다른 것들로 그렇게 할 때, 그것은 매개 변수의 폭발로 이어지고 일반적으로 더 많은 매개 변수의 경계로 이어 집니다.
1998 년 ECOOP에서 Kim Bruce, Phil Wadler와 저는 알지 못하는 것들의 수를 늘리면 전형적인 프로그램이 2 차적으로 성장할 것이라는 논문을 보았습니다 .
따라서 이차 폭발을 일으키지 않기 때문에 매개 변수를 수행하지 말고 이러한 추상 멤버를 갖는 이유는 매우 좋습니다.
thatismatt 는 의견을 묻습니다.
다음은 공정한 요약이라고 생각하십니까?
- 추상 유형은 'has-a'또는 'uses-a'관계에 사용됩니다 (예 : a
Cow eats Grass
).- 제네릭은 보통 관계 '의'(예를 그대로 곳
List of Ints
)
추상 형식이나 제네릭을 사용하는 것의 관계가 다른지 확실하지 않습니다. 다른 점은 다음과 같습니다.
마틴은 오는 "매개 변수의 폭발, 보통, 무엇에, 더 경우에 대해 말하는 것을 이해하기 위해 매개 변수의 범위 "및 추상 형식이 제네릭을 사용하여 모델링하는 경우 그 이후 차적으로 성장, 당신은 종이 "고려할 수있는 확장 가능한 구성 요소 추상화 "Partcom 프로젝트 (2007 년에 완성)에서 발간 된 Martin Odersky와 OOPSLA 2005의 Matthias Zenger에 의해 작성되었습니다 .
관련 추출물
추상 유형 멤버 는 구체적인 유형의 구성 요소를 유연하게 추상화 할 수있는 방법을 제공합니다.
추상 형식은 SML 서명 에서의 사용과 유사하게 구성 요소의 내부에 대한 정보를 숨길 수 있습니다 . 상속을 통해 클래스를 확장 할 수있는 객체 지향 프레임 워크에서 유연한 매개 변수화 수단으로 사용될 수도 있습니다 (종종 가족 다형성이라고 함, 예를 들어이 웹 로그 항목 및 Eric Ernst가 작성한 논문 참조 ).
(참고 : 재사용 가능하지만 형식이 안전한 상호 재귀 클래스를 지원하기위한 솔루션으로 객체 지향 언어에 대해 패밀리 다형성이 제안되었습니다. 가족 다형성
의 핵심 아이디어는 상호 재귀 클래스를 그룹화하는 데 사용되는 패밀리 개념입니다.)
abstract class MaxCell extends AbsCell {
type T <: Ordered { type O = T }
def setMax(x: T) = if (get < x) set(x)
}
여기서 T 의 형식 선언은 클래스 이름 Ordered 및 refinement로 구성된 상한 형식 바운드로 제한됩니다
{ type O = T }
.
상한은 서브 클래스에서 T의 특수화가 유형 멤버O
가 있는 Ordered의 서브 타입으로 제한equals T
됩니다.
이러한 제약 때문에<
Ordered 클래스 의 메서드는 수신자와 T 유형의 인수에 적용 할 수 있습니다.
이 예에서는 경계 형식 멤버 자체가 경계의 일부로 나타날 수 있음을 보여줍니다.
(즉, Scala는 F-bounded polymorphism을 지원합니다 )
(참고 : Peter Canning, William Cook, Walter Hill, Walter Olthoff 논문 :
Cardelli 및 Wegner는 지정된 유형의 모든 하위 유형에 대해 균일하게 작동하는 타이핑 기능의 수단으로 경계 수량화를 도입했습니다.
간단한 "객체"모델을 정의했습니다 . "속성". 지정된 세트를 가진 모든 객체에 메이크업 감각 있고 입력 체크하는 기능을 제한된 정량을 사용
의 요소 개체 수있는 것이 객체 지향 언어의보다 현실적인 표현 재귀 적 정의 유형 .
이러한 맥락에서, 경계 정량화는 더 이상 의도 된 목적에 부합하지 않으며, 지정된 일련의 분석법을 가지고 있지만 Cardelli-Wegner 시스템에 입력 할 수없는 모든 객체에 적합한 기능을 쉽게 찾을 수 있습니다.
객체 지향 언어로 형식화 된 다형성 함수에 대한 기초를 제공하기 위해 F- 바운드 정량화를 소개합니다)
프로그래밍 언어에는 두 가지 주요 추상화 형태가 있습니다.
첫 번째 형식은 기능적 언어에 일반적이며 두 번째 형식은 일반적으로 객체 지향 언어에 사용됩니다.
전통적으로 Java는 값에 대한 매개 변수화와 조작에 대한 멤버 추상화를 지원합니다. 제네릭이 포함 된 최신 Java 5.0은 유형에 대해서도 매개 변수화를 지원합니다.
스칼라에 제네릭을 포함시키는 데 대한 주장은 두 가지입니다.
첫째, 추상 형식으로 인코딩하는 것은 수작업으로 간단하지 않습니다. 간결함의 손실 외에도 형식 매개 변수를 에뮬레이트하는 추상 형식 이름간에 우연한 이름 충돌 문제가 있습니다.
둘째, 제네릭과 추상 형식은 일반적으로 스칼라 프로그램에서 고유 한 역할을합니다.
경계 다형성이있는 시스템에서 추상 형식을 제네릭으로 다시 작성 하면 형식 경계 의 2 차 확장 이 수반 될 수 있습니다 .
스칼라의 추상 형식 멤버와 일반 형식 매개 변수 (빌 공급 업체)
(강조 광산)
지금까지 추상 형식 멤버 에 대한 나의 관찰 은 다음과 같은 경우에 일반 형식 매개 변수보다 더 나은 선택이라는 것입니다.
- 당신은 사람들이 특성을 통해 이러한 유형의 정의를 혼합 하게하고 싶습니다 .
- 형식 멤버 이름을 명시 적으로 언급하면 코드 가독성에 도움이된다고 생각합니다 .
예:
세 개의 다른 조명기 객체를 테스트에 전달하려면 그렇게 할 수 있지만 각 매개 변수마다 하나씩 세 가지 유형을 지정해야합니다. 따라서 type 매개 변수 접근 방식을 취했으면 스위트 클래스가 다음과 같이 보일 수 있습니다.
// Type parameter version
class MySuite extends FixtureSuite3[StringBuilder, ListBuffer, Stack] with MyHandyFixture {
// ...
}
타입 멤버 접근 방식은 다음과 같습니다.
// Type member version
class MySuite extends FixtureSuite3 with MyHandyFixture {
// ...
}
추상 형식 멤버와 일반 형식 매개 변수의 또 다른 사소한 차이점은 일반 형식 매개 변수를 지정하면 코드 판독기에 형식 매개 변수의 이름이 표시되지 않는다는 것입니다. 따라서 누군가이 코드 줄을 보았습니다.
// Type parameter version
class MySuite extends FixtureSuite[StringBuilder] with StringBuilderFixture {
// ...
}
그들은 StringBuilder로 지정된 type 매개 변수의 이름이 그것을 찾지 않고 무엇인지 알 수 없었습니다. 형식 매개 변수의 이름은 추상 형식 멤버 접근 방식의 코드에 있습니다.
// Type member version
class MySuite extends FixtureSuite with StringBuilderFixture {
type FixtureParam = StringBuilder
// ...
}
후자의 경우 코드 독자
StringBuilder
는 "fixture parameter"유형 임을 알 수 있습니다 .
그들은 여전히 "fixture parameter"가 무엇을 의미하는지 알아 내야하지만, 문서를 보지 않고도 최소한 타입의 이름을 얻을 수 있습니다.
스칼라에 대해 읽을 때도 같은 질문이있었습니다.
제네릭 사용의 이점은 유형 패밀리를 작성한다는 것입니다. 아무도 서브 클래 싱 할 필요가 없습니다 Buffer
- 그들은 그냥 사용할 수 있습니다 Buffer[Any]
, Buffer[String]
등
추상 유형을 사용하는 경우 사람들은 서브 클래스를 작성해야합니다. 사람들은 같은 클래스가 필요합니다 AnyBuffer
, StringBuffer
등
특정 요구에 더 적합한 것을 결정해야합니다.
Buffer { type T <: String }
또는 Buffer { type T = String }
필요에 따라
추상 매개 변수를 유형 매개 변수와 함께 사용하여 사용자 정의 템플리트를 설정할 수 있습니다.
세 가지 특성이 연결된 패턴을 설정해야한다고 가정 해 봅시다.
trait AA[B,C]
trait BB[C,A]
trait CC[A,B]
형식 매개 변수에 언급 된 인수가 AA, BB, CC 자체를 존중하는 방식으로
다음과 같은 코드가 제공 될 수 있습니다.
trait AA[B<:BB[C,AA[B,C]],C<:CC[AA[B,C],B]]
trait BB[C<:CC[A,BB[C,A]],A<:AA[BB[C,A],C]]
trait CC[A<:AA[B,CC[A,B]],B<:BB[CC[A,B],A]]
유형 매개 변수 본드 때문에이 간단한 방식으로 작동하지 않습니다. 올바르게 상속하려면 공변량으로 만들어야합니다.
trait AA[+B<:BB[C,AA[B,C]],+C<:CC[AA[B,C],B]]
trait BB[+C<:CC[A,BB[C,A]],+A<:AA[BB[C,A],C]]
trait CC[+A<:AA[B,CC[A,B]],+B<:BB[CC[A,B],A]]
이 샘플은 컴파일되지만 분산 규칙에 대한 강력한 요구 사항을 설정하며 경우에 따라 사용할 수 없습니다
trait AA[+B<:BB[C,AA[B,C]],+C<:CC[AA[B,C],B]] {
def forth(x:B):C
def back(x:C):B
}
trait BB[+C<:CC[A,BB[C,A]],+A<:AA[BB[C,A],C]] {
def forth(x:C):A
def back(x:A):C
}
trait CC[+A<:AA[B,CC[A,B]],+B<:BB[CC[A,B],A]] {
def forth(x:A):B
def back(x:B):A
}
컴파일러는 다양한 분산 검사 오류로 이의를 제기합니다.
이 경우 추가 특성의 모든 유형 요구 사항을 수집하고 그 밖의 특성을 매개 변수화 할 수 있습니다.
//one trait to rule them all
trait OO[O <: OO[O]] { this : O =>
type A <: AA[O]
type B <: BB[O]
type C <: CC[O]
}
trait AA[O <: OO[O]] { this : O#A =>
type A = O#A
type B = O#B
type C = O#C
def left(l:B):C
def right(r:C):B = r.left(this)
def join(l:B, r:C):A
def double(l:B, r:C):A = this.join( l.join(r,this), r.join(this,l) )
}
trait BB[O <: OO[O]] { this : O#B =>
type A = O#A
type B = O#B
type C = O#C
def left(l:C):A
def right(r:A):C = r.left(this)
def join(l:C, r:A):B
def double(l:C, r:A):B = this.join( l.join(r,this), r.join(this,l) )
}
trait CC[O <: OO[O]] { this : O#C =>
type A = O#A
type B = O#B
type C = O#C
def left(l:A):B
def right(r:B):A = r.left(this)
def join(l:A, r:B):C
def double(l:A, r:B):C = this.join( l.join(r,this), r.join(this,l) )
}
이제 우리는 설명 된 패턴에 대한 구체적인 표현을 작성하고 모든 클래스에서 left 및 join 메소드를 정의하고 무료로 오른쪽 및 이중을 얻을 수 있습니다
class ReprO extends OO[ReprO] {
override type A = ReprA
override type B = ReprB
override type C = ReprC
}
case class ReprA(data : Int) extends AA[ReprO] {
override def left(l:B):C = ReprC(data - l.data)
override def join(l:B, r:C) = ReprA(l.data + r.data)
}
case class ReprB(data : Int) extends BB[ReprO] {
override def left(l:C):A = ReprA(data - l.data)
override def join(l:C, r:A):B = ReprB(l.data + r.data)
}
case class ReprC(data : Int) extends CC[ReprO] {
override def left(l:A):B = ReprB(data - l.data)
override def join(l:A, r:B):C = ReprC(l.data + r.data)
}
따라서 추상 유형과 유형 매개 변수는 모두 추상을 작성하는 데 사용됩니다. 둘 다 약점과 장점이 있습니다. 추상 유형은보다 구체적이며 모든 유형 구조를 설명 할 수 있지만 자세하고 명시 적으로 지정해야합니다. 형식 매개 변수는 여러 유형을 즉시 만들 수 있지만 상속 및 형식 범위에 대한 추가 걱정을 줄 수 있습니다.
그것들은 서로에게 시너지 효과를주고 그것들 중 하나만으로는 표현 될 수없는 복잡한 추상화를 생성하기 위해 함께 사용될 수 있습니다.