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

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

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

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

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

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

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



답변

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

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

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

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와 같은 언어는 좀 더 일반적인 방식으로 다른 형태의 의존적 타이핑을 가지고 있지만 스칼라와 달리 서브 타이핑이없는 타입 시스템의 일부이기 때문에 크게 다릅니다.


답변

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“내 라이브러리 포주”확장 프로그램을 수정할 수 없으며 작성 중이라고 가정합니다.

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


답변

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

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, "")
         ^


답변

모델 개발 환경 상태 선언적 프로그래밍의 한 형태의 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


답변