- 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.
- Bootstrap Site Blueprints Volume II
- Python量化投資指南:基礎、數據與實戰
- 測試驅動開發:入門、實戰與進階
- 兩周自制腳本語言
- Mastering Articulate Storyline
- 用Flutter極速構建原生應用
- Hands-On GPU:Accelerated Computer Vision with OpenCV and CUDA
- Spring快速入門
- Getting Started with LLVM Core Libraries
- MongoDB,Express,Angular,and Node.js Fundamentals
- Android移動開發案例教程:基于Android Studio開發環境
- Unity&VR游戲美術設計實戰
- Qt 5.12實戰
- 關系數據庫與SQL Server 2012(第3版)
- HTML5+CSS3+jQuery Mobile+Bootstrap開發APP從入門到精通(視頻教學版)