종속적 인 메소드 유형에 대한 강력한 유스 케이스는 무엇입니까?
이전 실험적인 기능으로 사용 종속 방법의 유형은 이제되었습니다 트렁크에 기본적으로 사용 하고, 분명히이 만든 것으로 보인다 일부 흥분 스칼라 지역 사회를.
처음에는 이것이 무엇이 유용한 지 즉시 알 수 없습니다. 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
}
It's an example of the classic cake pattern: we have a family of abstractions which are gradually refined through a heirarchy (ResourceManager
/Resource
are refined by FileManager
/File
which are in turn refined by NetworkFileManager
/RemoteFile
). It's a toy example, but the pattern is real: it's used throughout the Scala compiler and was used extensively in the Scala Eclipse plugin.
Here's an example of the abstraction in use,
val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)
Note that the path dependency means that the compiler will guarantee that the testHash
and testDuplicates
methods on NetworkFileManager
can only be called with arguments which correspond to it, ie. it's own RemoteFiles
, and nothing else.
That's undeniably a desirable property, but suppose we wanted to move this test code to a different source file? With dependent method types it's trivially easy to redefine those methods outside the ResourceManager
hierarchy,
def testHash4(rm : ResourceManager)(r : rm.Resource) =
assert(r.hash == "9e47088d")
def testDuplicates4(rm : ResourceManager)(r : rm.Resource) =
assert(r.duplicates(r))
Note the uses of dependent method types here: the type of the second argument (rm.Resource
) depends on the value of the first argument (rm
).
It is possible to do this without dependent method types, but it's extremely awkward and the mechanism is quite unintuitive: I've been teaching this course for nearly two years now, and in that time, noone has come up with a working solution unprompted.
Try it for yourself ...
// 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)
After a short while struggling with it you'll probably discover why I (or maybe it was David MacIver, we can't remember which of us coined the term) call this the Bakery of Doom.
Edit: consensus is that Bakery of Doom was David MacIver's coinage ...
For the bonus: Scala's form of dependent types in general (and dependent method types as a part of it) was inspired by the programming language Beta ... they arise naturally from Beta's consistent nesting semantics. I don't know of any other even faintly mainstream programming language which has dependent types in this form. Languages like Coq, Cayenne, Epigram and Agda have a different form of dependent typing which is in some ways more general, but which differs significantly by being part of type systems which, unlike Scala, don't have subtyping.
trait Graph {
type Node
type Edge
def end1(e: Edge): Node
def end2(e: Edge): Node
def nodes: Set[Node]
def edges: Set[Edge]
}
Somewhere else we can statically guarantee that we aren't mixing up nodes from two different graphs, e.g.:
def shortestPath(g: Graph)(n1: g.Node, n2: g.Node) = ...
Of course, this already worked if defined inside Graph
, but say we can't modify Graph
and are writing a "pimp my library" extension for it.
About the second question: types enabled by this feature are far weaker than complete dependent types (See Dependently Typed Programming in Agda for a flavor of that.) I don't think I've seen an analogy before.
This new feature is needed when concrete abstract type members are used instead of type parameters. When type parameters are used, the family polymorphism type dependency can be expressed in the latest and some older versions of Scala, as in the following simplified example.
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, "")
^
I'm developing a model for the interoption of a form of declarative programming with environmental state. The details aren't relevant here (e.g. details about callbacks and conceptual similarity to the Actor model combined with a Serializer).
The relevant issue is state values are stored in a hash map and referenced by a hash key value. Functions input immutable arguments that are values from the environment, may call other such functions, and write state to the environment. But functions are not allowed to read values from the environment (so the internal code of the function is not dependent on the order of state changes and thus remains declarative in that sense). How to type this in Scala?
The environment class must have an overloaded method which inputs such a function to call, and inputs the hash keys of the arguments of the function. Thus this method can call the function with the necessary values from the hash map, without providing public read access to the values (thus as required, denying functions the ability to read values from the environment).
But if these hash keys are strings or integer hash values, the static typing of the hash map element type subsumes to Any or AnyRef (hash map code not shown below), and thus a run-time mismatch could occur, i.e. it would be possible to put any type of value in a hash map for a given hash key.
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
}
Although I didn't test the following, in theory I can get the hash keys from class names at runtime employing classOf
, so a hash key is a class name instead of a string (using Scala's backticks to embed a string in a class name).
trait DependentHashKey {
type ValueType
}
trait `the hash key string` extends DependentHashKey {
type ValueType <: SomeType
}
So static type safety is achieved.
def callit[A](arg1key: DependentHashKey)(func: Env => arg1key.ValueType => A): A
'Programing' 카테고리의 다른 글
iOS 7.0 코드 서명 ID가 없습니다. (0) | 2020.07.10 |
---|---|
github에서 문제를 다시 여는 방법은 무엇입니까? (0) | 2020.07.10 |
커스텀 HTTP 인증 헤더 (0) | 2020.07.10 |
Pandas Multi-Index를 열로 변환 (0) | 2020.07.10 |
vim으로 새 줄에 붙여 넣는 방법? (0) | 2020.07.10 |