使用型別化的孔來定義類例項

通過互動式過程,鍵入的孔可以更容易地定義功能。

假設你要定義一個類例項 Foo Bar(對於你的自定義 Bar 型別,以便將其與一些需要 Foo 例項的多型庫函式一起使用)。你現在傳統上會查詢 Foo 的文件,找出你需要定義哪些方法,仔細檢查它們的型別等等 - 但是對於打字的洞,你實際上可以跳過它!

首先只定義一個虛擬例項:

instance Foo Bar where

編譯器現在會抱怨

Bar.hs:13:10: Warning:
No explicit implementation for
  ‘foom’ and ‘quun’
In the instance declaration for ‘Foo Bar’

好的,所以我們需要為 Bar 定義 foom。但是,什麼,即使應該是什麼?我們再一次懶得檢視文件,只要問編譯器:

instance Foo Bar where
  foom = _

在這裡,我們使用了一個打字的洞作為一個簡單的文件查詢。編譯器輸出

Bar.hs:14:10:
    Found hole ‘_’ with type: Bar -> Gronk Bar
    Relevant bindings include
      foom::Bar -> Gronk Bar (bound at Foo.hs:4:28)
    In the expression: _
    In an equation for ‘foom’: foom = _
    In the instance declaration for ‘Foo Bar’

請注意編譯器如何使用我們想要例項化的具體型別 Bar 填充類型別變數。這可以使簽名比類文件中的多型變數更容易理解,特別是如果你正在處理更復雜的方法,例如多引數型別類。

但到底是怎麼回事?在這一點上,問問 Hayoo 可能是個好主意。但是我們仍然可以在沒有這個的情況下轉義:作為盲目的猜測,我們假設這不僅是一個型別建構函式,而且是單值建構函式,即它可以用作以某種方式產生 Gronk a 值的函式。所以我們試試

instance Foo Bar where
  foom bar = _ Gronk

如果我們很幸運,Gronk 實際上是一個值,編譯器現在會說

    Found hole ‘_’
      with type: (Int -> [(Int, b0)] -> Gronk b0) -> Gronk Bar
    Where: ‘b0’ is an ambiguous type variable

好吧,這很難看 - 首先請注意 Gronk 有兩個引數,所以我們可以改進我們的嘗試:

instance Foo Bar where
  foom bar = Gronk _ _

現在這很清楚了:

    Found hole ‘_’ with type: [(Int, Bar)]
    Relevant bindings include
      bar::Bar (bound at Bar.hs:14:29)
      foom::Bar -> Gronk Bar (bound at Foo.hs:15:24)
    In the second argument of ‘Gronk’, namely ‘_’
    In the expression: Gronk _ _
    In an equation for ‘foom’: foom bar = Gronk _ _

你現在可以通過例如解構 bar 值來進一步推進(然後元件將在 Relevant bindings 部分中顯示型別)。通常情況下,在某種程度上,正確定義將是完全明顯的,因為你看到所有可用的論點和型別都像拼圖一樣融合在一起。或者,你可能會看到定義不可能以及原因。

所有這些在具有互動式編譯的編輯器中效果最佳,例如具有 haskell 模式的 Emacs。然後,你可以使用型別化的洞,就像 IDE 中的滑鼠懸停值查詢一樣,用於解釋的動態命令式語言,但沒有所有限制。