官术网_书友最值得收藏!

Path-dependent types

Until now, we have avoided talking about paths, mostly because they are not types themselves. However, they can be a part of named types, and thus have an important role in Scala's type system.

A path can have one of the following forms:

  • An empty path, denoted with ε. It cannot be written directly, but implicitly precedes any other path.
  • C.this, where C is a reference class. This is the path that is constructed if this is used inside of a class.
  • C.super.x. or C.super[P]. refers to the member x of the superclass or designated parent class, P of C. It plays the same role as this for the class, but refers to the classes that are upper in the hierarchy.
  • p.x, where p is a path and x is a stable member of p. The stable member is an object definition or a value definition for which it is possible for the compiler to tell that it will always be accessible (as opposed to the volatile type where it is not possible, for example, there is an abstract type definition that can be overridden by a subclass).

Types within the path can be referred to by two operators, # (hash) and . (dot). The former is known as type projection, and T#m refers to the type member m of the type T. We can demonstrate the difference between these operators by building a type-safe lock:

case class Lock() {
final case class Key()
def open(key: Key): Lock = this
def close(key: Key): Lock = this
def openWithMaster(key: Lock#Key): Lock = this
def makeKey: Key = new Key
def makeMasterKey: Lock#Key = new Key
}

Here, we defined a type, Lock, with a nested type, Key. The key can be referenced using its path, Lock.Key, or by using a projection, Lock#Key. The former denotes a type tied to a specific instance, and the latter denotes a type that is not. The specific types of key are returned by two different constructor methods. The makeKey return type is a Key that is a shortcut for this.Key, which in turn is an alias for Lock.this.type#Key and represents a path-dependent type. The latter is just a type projection, Lock#Key. Because the path-dependent type refers to the concrete instance, the compiler will only allow the use of the appropriate type to call the open and close methods:

val blue: Lock = Lock()
val red: Lock = Lock()
val blueKey: blue.Key = blue.makeKey
val anotherBlueKey: blue.Key = blue.makeKey
val redKey: red.Key = red.makeKey

blue.open(blueKey)
blue.open(anotherBlueKey)
blue.open(redKey) // compile error
red.open(blueKey) // compile error

The masterKey is not path-dependent, and so can be used to call methods on any instance in a typical way:

val masterKey: Lock#Key = red.makeMasterKey

blue.openWithMaster(masterKey)
red.openWithMaster(masterKey)

These path-dependent types conclude our journey regarding concrete types and can be used to describe values. All of the types we've seen so far (except method types) are named value types to reflect this fact. A named value type is called a type designator. All type designators are shorthand for type projections. 

We will now switch gears and inspect how types can be used to narrate definitions of other types.

主站蜘蛛池模板: 辰溪县| 仙居县| 理塘县| 电白县| 尚义县| 微博| 太仆寺旗| 和政县| 临江市| 洛隆县| 东海县| 海城市| 青龙| 阿图什市| 焦作市| 内江市| 儋州市| 昌乐县| 江西省| 三门峡市| 贡嘎县| 光泽县| 英山县| 攀枝花市| 五峰| 信丰县| 肇庆市| 台安县| 新乡市| 会理县| 城步| 禹州市| 云霄县| 项城市| 印江| 本溪市| 翁源县| 兰考县| 左权县| 宣化县| 略阳县|