概述
類型層次的編程與傳統,價值層次的編程有許多相似之處。但是,與運行時發生計算的值級編程不同,在類型級編程中,計算髮生在編譯時。我將嘗試在價值層次的編程和類型層次的編程之間進行比較。
範式
有在類型級別的編程兩個主要範式:「面向對象」和「功能」。從這裏鏈接到的大多數例子遵循面向對象的範例。
在面向對象範型級編程的一個很好的,很簡單的例子可以在apocalisp的implementation of the lambda calculus發現,這裏複製:
// Abstract trait
trait Lambda {
type subst[U <: Lambda] <: Lambda
type apply[U <: Lambda] <: Lambda
type eval <: Lambda
}
// Implementations
trait App[S <: Lambda, T <: Lambda] extends Lambda {
type subst[U <: Lambda] = App[S#subst[U], T#subst[U]]
type apply[U] = Nothing
type eval = S#eval#apply[T]
}
trait Lam[T <: Lambda] extends Lambda {
type subst[U <: Lambda] = Lam[T]
type apply[U <: Lambda] = T#subst[U]#eval
type eval = Lam[T]
}
trait X extends Lambda {
type subst[U <: Lambda] = U
type apply[U] = Lambda
type eval = X
}
正如在本例中可以看出,面向對象類型級編程的範例如下進行:
- 第一:用各種抽象類型字段定義一個抽象特徵(參見下文抽象字段是什麼)。這是一個用於保證所有實現中都存在某些類型字段而不強制執行的模板。在lambda微積分示例中,這對應於
trait Lambda
,它確保存在以下類型:subst
,apply
和eval
。
- 下一頁:定義擴展抽象性狀subtraits和實施各種抽象類型字段
- 通常,這些subtraits將與參數進行參數化。在演算例如,亞型是
trait App extends Lambda
其是參數化與兩種類型(S
和T
,兩者都必須是Lambda
亞型),trait Lam extends Lambda
參數與一種類型(T
),並trait X extends Lambda
(這不是參數化)。
- 類型字段通常通過引用減法類型參數來實現,有時通過哈希運算符引用其類型字段:
#
(與點運算符:值爲.
非常相似)。在lambda微積分示例的特徵App
中,類型eval
按如下實現:type eval = S#eval#apply[T]
。這本質上是調用eval
類型的特徵參數S
,並調用apply
與參數T
對結果。請注意,S
保證有一個eval
類型,因爲該參數指定它是Lambda
的子類型。類似地,eval
的結果必須具有apply
類型,因爲它被指定爲Lambda
的子類型,如抽象特徵Lambda
中所指定的那樣。
的功能範例包括定義許多未在特徵組合在一起,參數化類型構造的。
值電平編程和類型級編程的比較
- abstract class
- 值級別:
abstract class C { val x }
- 類型級:
trait C { type X }
- 路徑依賴類型
C.x
(引用字段值/功能在對象C X)
C#x
(在性狀ç引用字段類型爲x)
- 函數簽名(未實施)
- 值電平:
def f(x:X) : Y
- type-level:
type f[x <: X] <: Y
(這稱爲「類型構造函數」,通常發生在抽象特徵中)
- 函數實現
- 值級別:
def f(x:X) : Y = x
- 類型級:
type f[x <: X] = x
- 條件句
- 檢查平等
- 值級別:
a:A == b:B
- 類型級:
implicitly[A =:= B]
- 值級別:在運行時通過一個單元測試發生在JVM(即沒有運行時的錯誤):
- 在essense是斷言:
assert(a == b)
- 類型級:經由一個類型檢測在編譯器會發生(即沒有編譯器錯誤):
類型之間轉換和值
訣竅是使用隱式函數和值。基本情況通常是一個隱含的值,遞歸情況通常是一個隱式函數。事實上,類型級編程大量使用了implicits。
考慮這個例子(taken from metascala和apocalisp):
sealed trait Nat
sealed trait _0 extends Nat
sealed trait Succ[N <: Nat] extends Nat
這裏有自然數的皮亞諾編碼。也就是說,每個非負整數都有一個類型:0的特殊類型,即_0
;並且每個大於零的整數具有形式爲Succ[A]
的類型,其中A
是表示較小整數的類型。例如,代表2的類型將是:Succ[Succ[_0]]
(繼承者對代表零的類型應用兩次)。
我們可以別名各種自然數爲更方便的參考。例如:
type _3 = Succ[Succ[Succ[_0]]]
(這是像限定val
是一個函數的結果很多)
現在,假設我們要定義一個值級函數def toInt[T <: Nat](v : T)
這需要在一個參數值,v
,它符合Nat
並返回一個整數,表示在v
的類型中編碼的自然數。例如,如果我們的值爲val x:_3 = null
(null
,類型爲Succ[Succ[Succ[_0]]]
),我們希望toInt(x)
返回3
。
要實現toInt
,我們要充分利用以下類:
class TypeToValue[T, VT](value : VT) { def getValue() = value }
正如我們下面將看到的,將有來自TypeToValue
類構造的每個Nat
從_0
長達一個對象(例如)_3
,並且每個將存儲對應類型的值表示(即TypeToValue[_0, Int]
將存儲值0
,TypeToValue[Succ[_0], Int]
將存儲值1
等)。請注意,TypeToValue
由兩種類型參數化:T
和VT
。 T
對應於我們試圖賦予值的類型(在本例中爲Nat
),而VT
對應於我們分配給它的值的類型(在我們的示例中爲Int
)。
現在我們提出以下兩個隱含的定義:
implicit val _0ToInt = new TypeToValue[_0, Int](0)
implicit def succToInt[P <: Nat](implicit v : TypeToValue[P, Int]) =
new TypeToValue[Succ[P], Int](1 + v.getValue())
我們實現toInt
如下:
def toInt[T <: Nat](v : T)(implicit ttv : TypeToValue[T, Int]) : Int = ttv.getValue()
要了解toInt
是如何工作的,讓我們來看看它做什麼一對夫婦的投入:
val z:_0 = null
val y:Succ[_0] = null
當我們撥打toInt(z)
,編譯器查找TypeToValue[_0, Int]
類型的隱式參數ttv
(因爲z
的類型爲_0
)。它找到對象_0ToInt
,它調用該對象的getValue
方法並取回0
。需要注意的重要一點是,我們沒有向程序指定使用哪個對象,編譯器隱式地發現它。
現在讓我們考慮一下toInt(y)
。這一次,編譯器查找類型爲TypeToValue[Succ[_0], Int]
的隱式參數ttv
(因爲y
的類型爲Succ[_0]
)。它找到函數succToInt
,它可以返回適當類型的對象(TypeToValue[Succ[_0], Int]
)並對其進行評估。該函數本身採用TypeToValue[_0, Int]
類型的隱式參數(v
)(即TypeToValue
,其中第一個參數的類型爲Succ[_]
)。編譯器提供_0ToInt
(如上面對toInt(z)
的評估中所做的那樣),並且succToInt
構造了一個新的TypeToValue
對象,其值爲1
。同樣,重要的是要注意編譯器隱式提供所有這些值,因爲我們沒有顯式地訪問它們。
檢查你的工作
有幾種方法來驗證你的類型,級別的計算都在做你的期望。這裏有幾種方法。使兩種類型A
和B
,你想驗證是相等的。然後,檢查以下編譯:
Equal[A, B]
implicitly[A =:= B]
或者,您可以將類型轉換爲數值(如上所示)並對這些值進行運行時檢查。例如。 assert(toInt(a) == toInt(b))
,其中a
是類型A
和b
是B
類型。
其他資源
一套完整的可構建可在the scala reference manual (pdf)類型部分找到。
Adriaan Moors具有大約從階類型構造和相關主題與實例幾個學術論文:
Apocalisp是一個博客,有許多類型級專業版的例子在scala中編寫。
ScalaZ是提供使用各種類型級編程功能延伸Scala的API功能一個非常活躍的項目。這是一個非常有趣的項目,有一個很大的關注。
MetaScala是Scala的一個類型級庫,包括自然數的元類型,布爾值,單位,HList等。這是一個由Jesper Nordenberg (his blog)開發的項目。
的Michid (blog)具有型電平編程的Scala中一些真棒的例子(從其他的答案):
Debasish Ghosh (blog)有一些相關的帖子,以及:
(我一直在做這方面的一些研究,這裏是我學到的。我仍然對它不熟悉,所以請指出此答案中的任何不準確之處。)
社區wiki? – 2010-12-11 09:01:37
就我個人而言,我發現有人想要在Scala中進行類型編程已經知道如何在Scala中編程非常合理。即使這意味着我不理解你鏈接到的那些文章的一個詞:-) – 2010-12-11 12:58:17