- Learn Scala Programming
- Slava Schmidt
- 432字
- 2021-06-10 19:35:46
Higher kinded types
Our example of the glass has become a bit boring. To make it fascinating again, we'll add another abstraction, a jar. This is how our model will look after that:
sealed trait Contents
case class Water(purity: Int) extends Contents
case class Whiskey(label: String) extends Contents
sealed trait Container[C <: Contents] { def contents: C }
case class Glass[C<: Contents](contents: C) extends Container[C]
case class Jar[C <: Contents](contents: C) extends Container[C]
The glass and the jar can both be filled with any contents. For instance, this is how it can be done:
def fillGlass[C <: Contents](c: C): Glass[C] = Glass(c)
def fillJar[C <: Contents](c: C): Jar[C] = Jar(c)
As we can see, both methods look identical with respect to the type used to construct the result. The parameterized type that is used to construct types is called a type constructor. As a consistent language, Scala allows you to abstract over type constructors in the same way it allows you to abstract over functions via higher order functions (more about this in the next chapter). This abstraction over type constructors is called higher kinded types. The syntax requires us to use an underscore to denote the expected type parameter on the definition side. The implementation should then use the type constructor without type constraints.
We can use a type constructor to provide a generic filling functionality. Of course, we can't get rid of the specific knowledge about how to fill our containers, but we can move it to the type level:
sealed trait Filler[CC[_]] {
def fill[C](c: C): CC[C]
}
object GlassFiller extends Filler[Glass] {
override def fill[C](c: C): Glass[C] = Glass(c)
}
object JarFiller extends Filler[Jar] {
override def fill[C](c: C): Jar[C] = Jar(c)
}
In the preceding code, we're using the type constructor CC[_] to denote both Glass and Jar in the Filler trait. We can now use created abstractions to define a generic filling functionality:
def fill[C, G[_]](c: C)(F: Filler[G]): G[C] = F.fill(c)
The G[_] type is a type constructor for glass and jar, and Filler[G] is a higher order type that uses this type constructor to build a full G[C] for any content, C. This is how the generic fill method can be used in practice:
val fullGlass: Glass[Int] = fill(100)(GlassFiller)
val fullJar: Jar[Int] = fill(200)(JarFiller)
This might not look like a huge win over the specific methods for now because we've provided our type constructors explicitly. The real advantage will become obvious the moment we start talking about implicits in Chapter 4, Getting to know Implicits and Type Classes.
- Learning Python Web Penetration Testing
- 小程序實戰視頻課:微信小程序開發全案精講
- FreeSWITCH 1.6 Cookbook
- Git高手之路
- Mastering Ubuntu Server
- GeoServer Beginner's Guide(Second Edition)
- Java編程技術與項目實戰(第2版)
- R Data Analysis Cookbook(Second Edition)
- HTML5秘籍(第2版)
- 移動互聯網軟件開發實驗指導
- HTML5開發精要與實例詳解
- Python Essentials
- Java并發編程:核心方法與框架
- Julia High Performance(Second Edition)
- C語言程序設計教程