종속적 인 메소드 유형에 대한 강력한 유스 케이스는 무엇입니까?


127

이전에는 실험적인 기능이었던 의존적 메소드 유형이 이제 기본적으로 트렁크에서 활성화되었으며 , 이는 약간의 흥분을 불러 일으켰습니다. 스칼라 지역 사회를.

처음에는 이것이 무엇이 유용한 지 즉시 알 수 없습니다. Heiko Seeberger는 여기 에 의존적 인 메소드 유형의 간단한 예를 게시 했습니다. 코멘트에서 볼 수 있듯이 메소드의 유형 매개 변수를 사용하여 쉽게 재현 할 수 있습니다. 따라서 이는 매우 매력적인 예가 아닙니다. (명백한 것이 누락되었을 수 있습니다. 그렇다면 수정 해주세요.)

대안에 비해 분명히 유리한 종속적 방법 유형에 대한 사용 사례의 실용적이고 유용한 예는 무엇입니까?

전에는 불가능했던 쉬운 일들을 어떻게 할 수 있습니까?

기존 유형 시스템 기능을 통해 무엇을 구매합니까?

또한 Haskell, OCaml과 같은 다른 고급 유형 언어의 유형 시스템에서 찾을 수있는 기능과 유사하거나 영감을받는 종속 방법 유형이 있습니까?


당신은 정독에 관심이있을 수 있습니다 haskell.org/haskellwiki/Dependent_type
댄 버튼을

링크 주셔서 감사합니다, 댄! 나는 일반적으로 종속 유형을 알고 있지만 종속 메소드 유형의 개념은 비교적 나에게 새로운 것입니다.
missingfaktor

"종속 메소드 유형"은 단순히 메소드의 입력 유형 중 하나에 의존하는 유형 인 것 같습니다 (메소드가 호출되는 객체의 유형 포함). 의존적 유형의 일반적인 아이디어를 넘어서는 미친 것은 없습니다. 아마도 뭔가 빠졌습니까?
Dan Burton

아뇨,하지 않았지만 분명히 했어요 :-) 나는 전에 둘 사이의 연결을 보지 못했습니다. 그래도 지금은 분명합니다.
missingfaktor

답변:


112

멤버 (즉, 중첩) 형식을 사용하면 의존적 메서드 형식이 필요할 수 있습니다. 특히, 나는 의존적 인 방법 유형이 없으면 고전적인 케이크 패턴이 반 패턴에 더 가깝다고 유지합니다.

그래서 무엇이 문제입니까? 스칼라의 중첩 유형은 둘러싼 인스턴스에 따라 다릅니다. 결과적으로 종속 메소드 유형이없는 경우 해당 메소드 외부에서이를 사용하려는 시도는 매우 어려울 수 있습니다. 이것은 처음에는 우아하고 매력적으로 보이는 디자인을 악몽처럼 단단하고 리팩토링하기 어려운 괴물로 바뀔 수 있습니다.

고급 스칼라 훈련 과정 에서 제공하는 운동을 통해

trait ResourceManager {
  type Resource <: BasicResource
  trait BasicResource {
    def hash : String
    def duplicates(r : Resource) : Boolean
  }
  def create : Resource

  // Test methods: exercise is to move them outside ResourceManager
  def testHash(r : Resource) = assert(r.hash == "9e47088d")  
  def testDuplicates(r : Resource) = assert(r.duplicates(r))
}

trait FileManager extends ResourceManager {
  type Resource <: File
  trait File extends BasicResource {
    def local : Boolean
  }
  override def create : Resource
}

class NetworkFileManager extends FileManager {
  type Resource = RemoteFile
  class RemoteFile extends File {
    def local = false
    def hash = "9e47088d"
    def duplicates(r : Resource) = (local == r.local) && (hash == r.hash)
  }
  override def create : Resource = new RemoteFile
}

그것은 고전적인 케이크 패턴의 예 : 우리는 점차적으로 계층 구조를 통해 정제하는 추상화의 가족이 ( ResourceManager/ Resource에 의해 정제되어 FileManager/ File에 의해 정제 차례로있는 NetworkFileManager/ RemoteFile). 장난감 예제이지만 패턴은 실제입니다. Scala 컴파일러 전체에서 사용되며 Scala Eclipse 플러그인에서 광범위하게 사용되었습니다.

다음은 사용중인 추상화의 예입니다.

val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)

경로 의존성은 컴파일러가 on testHashtestDuplicates메소드 NetworkFileManager가 이에 대응하는 인수로만 호출 될 수 있음을 보증한다는 것을 참고하십시오 . 그것은 자신의 RemoteFiles것이고 다른 것은 없습니다.

이것은 바람직한 속성이지만,이 테스트 코드를 다른 소스 파일로 옮기고 싶었다고 가정 해보십시오. 종속 메소드 유형을 사용하면 ResourceManager계층 외부에서 해당 메소드를 쉽게 재정의 할 수 있습니다 .

def testHash4(rm : ResourceManager)(r : rm.Resource) = 
  assert(r.hash == "9e47088d")

def testDuplicates4(rm : ResourceManager)(r : rm.Resource) = 
  assert(r.duplicates(r))

여기에서 종속 메소드 유형의 사용에 주목하십시오. 두 번째 인수 ( rm.Resource) 의 유형은 첫 번째 인수 ( rm) 의 값에 따라 다릅니다 .

의존적 인 메소드 유형 없이이 작업을 수행 할 수는 있지만 매우 어색하고 메커니즘은 매우 직관적이지 않습니다. 나는 거의 2 년 동안이 과정을 가르치고 있으며 그 당시 아무도 신속하게 작동하는 솔루션을 찾지 못했습니다.

직접 해보십시오 ...

// Reimplement the testHash and testDuplicates methods outside
// the ResourceManager hierarchy without using dependent method types
def testHash        // TODO ... 
def testDuplicates  // TODO ...

testHash(rf)
testDuplicates(rf)

잠시 동안 어려움을 겪고 나면 왜 내가 (또는 아마도 데이비드 맥 이버 였는지, 우리가 어느 용어를 만든 지 기억 나지 않습니다) 이것을 이것을 빵집 베이커리라고 부릅니다.

편집 : 합의는 운명의 빵집이 David MacIver의 주화였습니다 ...

보너스 : 스칼라의 종속 유형 (종종 종속 메소드 유형)은 프로그래밍 언어 베타 에서 영감을 얻었습니다 . 베타의 일관된 중첩 의미론에서 자연스럽게 발생합니다. 나는이 형태의 의존 유형을 가진 희미한 주류 프로그래밍 언어조차 모른다. Coq, Cayenne, Epigram 및 Agda와 같은 언어는 좀 더 일반적인 방식으로 다른 형태의 의존적 타이핑을 가지고 있지만 스칼라와 달리 서브 타이핑이없는 타입 시스템의 일부이기 때문에 크게 다릅니다.


2
이 용어를 만든 사람은 David MacIver 였지만 어쨌든 그것은 매우 설명 적입니다. 이것은 종속 메소드 유형이 그렇게 흥미로운 이유에 대한 환상적인 설명입니다. 잘 하셨어요!
Daniel Spiewak

그것은 꽤 오래 전에 #scala에서 우리 둘 사이의 대화에서 처음 등장했습니다 ... 내가 말한 것처럼 우리 중 누가 먼저 말했는지 기억이 나지 않습니다.
Miles Sabin

내 기억이 내게 장난을 치고있는 것 같다.
Miles Sabin

예, 당시에는 #scala에 없었지만 Jorge가 있었고 그곳에서 정보를 얻었습니다.
Daniel Spiewak

추상 유형 멤버 개선을 사용하여 testHash4 기능을 상당히 쉽게 구현할 수있었습니다. def testHash4[R <: ResourceManager#BasicResource](rm: ResourceManager { type Resource = R }, r: R) = assert(r.hash == "9e47088d")그러나 이것이 다른 유형의 종속 유형으로 간주 될 수 있다고 생각합니다.
Marco van Hilst

53
trait Graph {
  type Node
  type Edge
  def end1(e: Edge): Node
  def end2(e: Edge): Node
  def nodes: Set[Node]
  def edges: Set[Edge]
}

다른 곳에서 우리는 두 개의 서로 다른 그래프에서 노드가 섞이지 않는다는 것을 정적으로 보장 할 수 있습니다.

def shortestPath(g: Graph)(n1: g.Node, n2: g.Node) = ... 

물론 이것은 내부 Graph에 정의되어 있으면 이미 작동 했지만 Graph"내 라이브러리 포주"확장 프로그램을 수정할 수 없으며 작성 중이라고 가정합니다.

두 번째 질문에 대해 :이 기능으로 활성화 된 유형은 완전한 종속 유형보다 훨씬 약합니다 ( 아그 다의 의존적 유형 프로그래밍 참조 ). 필자는 이전에 비유를 본 적이 없다고 생각합니다.


6

이 새로운 기능은 유형 매개 변수 대신 구체적인 추상 유형 구성원을 사용할 때 필요합니다 . 유형 매개 변수를 사용 하는 경우 다음 단순화 된 예와 같이 패밀리 다형성 유형 종속성이 최신 및 일부 이전 버전의 스칼라로 표현 될 수 있습니다.

trait C[A]
def f[M](a: C[M], b: M) = b
class C1 extends C[Int]
class C2 extends C[String]

f(new C1, 0)
res0: Int = 0
f(new C2, "")
res1: java.lang.String = 
f(new C1, "")
error: type mismatch;
 found   : C1
 required: C[Any]
       f(new C1, "")
         ^

이것은 관련이 없습니다. 형식 멤버를 사용하면 동일한 결과를 위해 구체화를 사용할 수 있습니다 trait C {type A}; def f[M](a: C { type A = M}, b: M) = 0;class CI extends C{type A=Int};class CS extends C{type A=String}.
nafg

어쨌든 이것은 종속 메소드 유형과 관련이 없습니다. 예를 들어 Alexey의 예를 보자 ( stackoverflow.com/a/7860821/333643 ). 귀하의 접근 방식 (내가 언급 한 개선 버전 포함)을 사용하는 것이 목표를 달성하지 못합니다. n1.Node = : = n2.Node를 보장하지만 둘 다 동일한 그래프에 있는지 확인하지는 않습니다. IIUC DMT는이를 보장합니다.
nafg

@nafg 지적 해 주셔서 감사합니다. 타입 멤버에 대한 구체화 사례를 언급하지 않았 음을 분명히하기 위해 구체적 이라는 단어를 추가했습니다 . 내가 알 수있는 한, 이것은 다른 유스 케이스에서 더 많은 힘을 가질 수 있다는 점 (여전히 알고 있었음)에도 불구하고 여전히 종속 메소드 유형에 대한 유효한 유스 케이스입니다. 아니면 두 번째 의견의 기본 본질을 놓치셨습니까?
쉘비 무어 III

3

모델 개발 환경 상태 선언적 프로그래밍의 한 형태의 interoption를 들어. 자세한 내용은 여기와 관련이 없습니다 (예 : 시리얼 라이저와 결합 된 액터 모델과의 콜백 및 개념적 유사성에 대한 세부 사항).

관련 문제는 상태 값이 해시 맵에 저장되고 해시 키 값으로 참조된다는 것입니다. 함수는 환경으로부터의 값인 불변 인수를 입력하고, 그러한 다른 함수를 호출하고, 환경에 상태를 쓸 수 있습니다. 그러나 함수는 환경에서 값 을 읽을 수 없습니다 (따라서 함수의 내부 코드는 상태 변경 순서에 의존하지 않으므로 그런 의미에서 선언적입니다). Scala에 이것을 입력하는 방법?

환경 클래스에는 호출 할 함수를 입력하고 함수 인수의 해시 키를 입력하는 오버로드 된 메소드가 있어야합니다. 따라서이 메소드는 값에 대한 공개 읽기 액세스를 제공하지 않고 해시 맵에서 필요한 값으로 함수를 호출 할 수 있습니다 (필요한 경우 함수가 환경에서 값을 읽을 수있는 기능을 거부 함).

그러나이 해시 키는 문자열이나 정수 해시 값, 해시지도 요소 유형의 정적 입력하면 포섭 하여 (아래에 표시되지 해시 맵 코드) 일부 또는 AnyRef, 그리고 런타임 불일치가 발생할 수, 즉, 그것은 가능할 것이다 주어진 해시 키에 대한 해시 맵에 모든 유형의 값을 넣습니다.

trait Env {
...
  def callit[A](func: Env => Any => A, arg1key: String): A
  def callit[A](func: Env => Any => Any => A, arg1key: String, arg2key: String): A
}

다음을 테스트하지는 않았지만 이론적으로 런타임을 사용하여 클래스 이름에서 해시 키를 얻을 수 classOf있으므로 해시 키는 문자열 대신 클래스 이름입니다 (스칼라의 백틱을 사용하여 클래스 이름에 문자열 포함).

trait DependentHashKey {
  type ValueType
}
trait `the hash key string` extends DependentHashKey {
  type ValueType <: SomeType
}

따라서 정적 유형 안전이 달성됩니다.

def callit[A](arg1key: DependentHashKey)(func: Env => arg1key.ValueType => A): A

인수 키를 단일 값으로 전달해야 할 때 테스트하지는 않았지만 Tuple을 사용할 수 있다고 가정합니다 (예 : 2 인수 과부하) def callit[A](argkeys: Tuple[DependentHashKey,DependentHashKey])(func: Env => argkeys._0.ValueType => argkeys._1.ValueType => A): A. 요소 유형은 컬렉션 유형에서 (컴파일 타임에 알려지지 않음) 포함되기 때문에 인수 키 컬렉션을 사용하지 않습니다.
쉘비 무어 III

"해시 맵 요소 유형의 정적 타이핑은 Any 또는 AnyRef에 포함됩니다"– 나는 따르지 않습니다. 요소 유형을 말할 때 키 유형 또는 값 유형을 의미합니까 (즉, HashMap에 대한 첫 번째 또는 두 번째 유형 인수)? 그리고 왜 그것을 습득했을까요?
Robin Green

@RobinGreen 해시 테이블의 값 유형입니다. Afair는 스칼라에 공용 (분리) 유형이 없기 때문에 공통 수퍼 타입에 속하지 않는 한 스칼라의 콜렉션에 둘 이상의 유형을 넣을 수 없기 때문에 추정되었습니다. Scala의 Subsumption에 대한 내 Q & A를 참조하십시오.
쉘비 무어 III
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.