명령형 대 일반 이전 하위 유형
내 친구가 지난주에 좋은 대답이없는 겉보기에 무해한 스칼라 언어 질문을 제기했습니다. 공통 유형 클래스에 속하는 것들의 모음을 선언하는 쉬운 방법이 있는지 여부입니다. 물론 스칼라에는 "타입 클래스"라는 일류 개념이 없기 때문에 특성과 컨텍스트 경계 (즉, 암시 적) 측면에서 이것을 생각해야합니다.
구체적으로, 어떤 특성 주어진 T[_]
typeclass를 나타내는 및 유형을 A
, B
및 C
, 범위에 implicits 대응으로 T[A]
, T[B]
그리고 T[C]
, 우리는 같은 것을 선언 할 List[T[a] forAll { type a }]
우리의 인스턴스를 던질 수있는으로, A
, B
와 C
무사를. 물론 이것은 스칼라에 존재하지 않습니다. 작년에 한 질문 에서 이에 대해 더 자세히 논의합니다.
자연스러운 후속 질문은 "하스켈이 어떻게 하는가?"입니다. 특히 GHC 는 "Boxy Types" 논문에 설명 된 impredicative polymorphism 이라는 유형 시스템 확장을 가지고 있습니다. 간단히 말해, typeclass가 주어지면 합법적으로 list를 구성 할 수 있습니다 . 이 형식의 선언이 주어지면 컴파일러는 런타임에 목록에있는 각 값의 유형에 해당하는 유형 클래스 인스턴스를 유지할 수있는 사전 전달 마법을 수행합니다.T
[forall a. T a => a]
문제는 "사전 통과 마법"이 "vtables"와 비슷하게 들린다는 것입니다. Scala와 같은 객체 지향 언어에서 하위 유형 지정은 "Boxy 유형"접근 방식보다 훨씬 더 간단하고 자연스러운 메커니즘입니다. 우리의 경우 A
, B
및 C
모든 특성 확장 T
, 우리는 간단하게 선언 할 수 있습니다 List[T]
행복합니다. 마찬가지로, 그들은 모든 특성을 확장 할 경우, 아래의 코멘트에있는 마일 노트로 T1
, T2
그리고 T3
나서 사용할 수 List[T1 with T2 with T3]
impredicative 하스켈에 동등하게 [forall a. (T1 a, T2 a, T3 a) => a]
.
그러나 typeclasses에 비해 하위 유형과 주요 잘 알려진 단점은 꽉 커플 링은 다음과 같습니다 내 A
, B
그리고 C
종류가있다 할 T
행동에 구운 이제이 주요 dealbreaker 가정하자, 나는. 할 수없는 하위 유형 사용합니다. 스칼라의 중간 포주 ^ H ^ H ^ H ^ H ^ Himplicit 변환되도록 : 일부를 주어진 A => T
, B => T
그리고 C => T
암시 적 범위에, 다시 아주 행복하게을 채울 수 있습니다 List[T]
내와 A
, B
및 C
값 ...
... 우리가 원할 때까지 List[T1 with T2 with T3]
. 우리가 암시 적 변환이 있더라도 그 시점에서 A => T1
, A => T2
그리고 A => T3
, 우리는 넣을 수없는 A
목록에. 암시 적 변환을 재구성하여 문자 그대로를 제공 할 수 A => T1 with T2 with T3
있지만, 이전에는 누구도 그렇게하는 것을 본 적이 없으며, 이는 또 다른 형태의 긴밀한 결합처럼 보입니다.
좋아요, 제 질문은 마지막으로 여기에서 이전에 질문했던 몇 가지 질문의 조합 인 것 같습니다. "왜 하위 입력을 피합니까?" 그리고 "타입 클래스에 대한 서브 타이핑의 장점" ... 명령형 다형성과 서브 타입 다형성이 하나이며 동일하다는 통합 이론이 있습니까? 암묵적 회심은 어떻게 든 두 사람의 은밀한 사랑의 자녀입니까? 그리고 누군가 Scala에서 다중 경계 (위의 마지막 예에서와 같이)를 표현하기 위해 훌륭하고 깨끗한 패턴을 표현할 수 있습니까?
명령형 유형과 실존 적 유형을 혼동하고 있습니다. 명령형 유형을 사용하면 임의의 구체적인 값이 아닌 데이터 구조 에 다형성 값 을 넣을 수 있습니다. 즉 [forall a. Num a => a]
, 각 요소가 숫자 유형으로 작동하는 목록이 있으므로 예 Int
및 Double
유형 목록에 [forall a. Num a => a]
넣을 수는 없지만 비슷한 것을 넣을 수 있습니다 0 :: Num a => a
. 명령형 유형은 여기서 원하는 것이 아닙니다.
여러분이 원하는 것은 실존 적 유형, 즉 [exists a. Num a => a]
(실제 Haskell 구문이 아님) 각 요소가 알려지지 않은 숫자 유형이라는 것을 말합니다. 그러나 이것을 Haskell로 작성하려면 래퍼 데이터 유형을 도입해야합니다.
data SomeNumber = forall a. Num a => SomeNumber a
에서 exists
로 변경된 것을 확인하십시오 forall
. 우리가 생성자를 설명하고 있기 때문 입니다. 우리는 모든 숫자 유형 넣을 수 있습니다 에서를 ,하지만 타입 시스템 "잊어"그것이 입력한다. 일단 우리가 (패턴 매칭에 의해) 그것을 제거하면, 우리가 아는 것은 그것이 어떤 숫자 유형이라는 것입니다. 내부에서 일어나고 SomeNumber
있는 것은 유형에 유형 클래스 사전 (일명 vtable / implicit)을 저장하는 숨겨진 필드가 포함되어 있기 때문에 래퍼 유형이 필요합니다.
이제 [SomeNumber]
임의의 숫자 목록에이 유형 을 사용할 수 있지만, 예를 들어 [SomeNumber (3.14 :: Double), SomeNumber (42 :: Int)]
. 각 유형에 대한 올바른 사전이 검색되어 각 숫자를 래핑하는 지점에서 자동으로 숨겨진 필드에 저장됩니다.
유형 클래스와 인터페이스의 주요 차이점은 유형 클래스를 사용하면 vtable이 객체와 별도로 이동하고 존재 유형이 객체와 vtable을 다시 함께 패키지화한다는 점에서 실존 유형과 유형 클래스의 조합은 일부면에서 하위 유형과 유사합니다.
그러나 기존의 하위 유형 지정과 달리 일대일로 쌍을 이룰 필요가 없으므로 하나의 vtable을 동일한 유형의 두 값으로 패키지화하는 이와 같은 것을 작성할 수 있습니다.
data TwoNumbers = forall a. Num a => TwoNumbers a a
f :: TwoNumbers -> TwoNumbers
f (TwoNumbers x y) = TwoNumbers (x+y) (x*y)
list1 = map f [TwoNumbers (42 :: Int) 7, TwoNumbers (3.14 :: Double) 9]
-- ==> [TwoNumbers (49 :: Int) 294, TwoNumbers (12.14 :: Double) 28.26]
또는 더 멋진 것들. 래퍼에 대한 패턴 일치가 끝나면 유형 클래스의 땅으로 돌아갑니다. 우리가 어떤 유형을 알고하지 않지만 x
하고 y
, 우리는 그들이 같은 거 알아, 우리는 그들에 수치 연산을 수행 할 수 올바른 사전이있다.
위의 모든 것은 여러 유형 클래스에서 유사하게 작동합니다. 컴파일러는 단순히 각 vtable의 래퍼 유형에 숨겨진 필드를 생성하고 패턴 일치시 모두 범위로 가져옵니다.
data SomeBoundedNumber = forall a. (Bounded a, Num a) => SBN a
g :: SomeBoundedNumber -> SomeBoundedNumber
g (SBN n) = SBN (maxBound - n)
list2 = map g [SBN (42 :: Int32), SBN (42 :: Int64)]
-- ==> [SBN (2147483605 :: Int32), SBN (9223372036854775765 :: Int64)]
저는 Scala에 관해서는 매우 초보자이기 때문에 질문의 마지막 부분을 도울 수 있을지 모르겠지만 이것이 적어도 혼란을 해결하고 방법에 대한 몇 가지 아이디어를 주셨기를 바랍니다. 계속하려면.
@hammar의 대답은 완벽합니다. 여기에 그것을 수행하는 스칼라 방식이 있습니다. 예를 들어 내가 할게요 Show
유형 클래스와 값으로 i
하고 d
목록에서 팩 :
// The type class
trait Show[A] {
def show(a : A) : String
}
// Syntactic sugar for Show
implicit final class ShowOps[A](val self : A)(implicit A : Show[A]) {
def show = A.show(self)
}
implicit val intShow = new Show[Int] {
def show(i : Int) = "Show of int " + i.toString
}
implicit val stringShow = new Show[String] {
def show(s : String) = "Show of String " + s
}
val i : Int = 5
val s : String = "abc"
우리가 원하는 것은 다음 코드를 실행할 수있는 것입니다.
val list = List(i, s)
for (e <- list) yield e.show
Building the list is easy but the list won't "remember" the exact type of each of its elements. Instead it will upcast each element to a common super type T
. The more precise super super type between String
and Int
being Any
, the type of the list is List[Any]
.
The problem is: what to forget and what to remember? We want to forget the exact type of the elements BUT we want to remember that they are all instances of Show
. The following class does exactly that
abstract class Ex[TC[_]] {
type t
val value : t
implicit val instance : TC[t]
}
implicit def ex[TC[_], A](a : A)(implicit A : TC[A]) = new Ex[TC] {
type t = A
val value = a
val instance = A
}
This is an encoding of the existential :
val ex_i : Ex[Show] = ex[Show, Int](i)
val ex_s : Ex[Show] = ex[Show, String](s)
It pack a value with the corresponding type class instance.
Finally we can add an instance for Ex[Show]
implicit val exShow = new Show[Ex[Show]] {
def show(e : Ex[Show]) : String = {
import e._
e.value.show
}
}
The import e._
is required to bring the instance into scope. Thanks to the magic of implicits:
val list = List[Ex[Show]](i , s)
for (e <- list) yield e.show
which is very close to the expected code.
참고URL : https://stackoverflow.com/questions/9732766/impredicative-types-vs-plain-old-subtyping
'development' 카테고리의 다른 글
2D 오목 선체를 생성하는 효율적인 알고리즘이 있습니까? (0) | 2020.12.09 |
---|---|
docker는 VM이 아닙니다. 컨테이너에 기본 이미지 OS가 필요한 이유는 무엇입니까? (0) | 2020.12.09 |
인덱스로 인해 레코드 수가 증가함에 따라 SQLite 삽입 속도가 느려집니다. (0) | 2020.12.09 |
무엇을 사용합니까? (0) | 2020.12.09 |
고성능 동시 MultiMap Java / Scala (0) | 2020.12.09 |