[英]Is it possible to use church encodings without breaking equational reasoning?
記住這個計划:
{-# LANGUAGE RankNTypes #-}
import Prelude hiding (sum)
type List h = forall t . (h -> t -> t) -> t -> t
sum_ :: (Num a) => List a -> a
sum_ = \ list -> list (+) 0
toList :: [a] -> List a
toList = \ list cons nil -> foldr cons nil list
sum :: (Num a) => [a] -> a
-- sum = sum_ . toList -- does not work
sum = \ a -> sum_ (toList a) -- works
main = print (sum [1,2,3])
總和的兩個定義都是相同的,直到等式推理。 然而,編譯第二個作品的定義,但第一個定義沒有,這個錯誤:
tmpdel.hs:17:14:
Couldn't match type ‘(a -> t0 -> t0) -> t0 -> t0’
with ‘forall t. (a -> t -> t) -> t -> t’
Expected type: [a] -> List a
Actual type: [a] -> (a -> t0 -> t0) -> t0 -> t0
Relevant bindings include sum :: [a] -> a (bound at tmpdel.hs:17:1)
In the second argument of ‘(.)’, namely ‘toList’
In the expression: sum_ . toList
似乎RankNTypes
打破了等式推理。 有沒有辦法在Haskell中有教會編碼的列表而不破壞它?
我的印象是ghc盡可能地滲透所有人:
forall a t. [a] -> (a -> t -> t) -> t -> t)
和
forall a. [a] -> forall t . (h -> t -> t) -> t -> t
可以互換使用,見證者:
toList' :: forall a t. [a] -> (a -> t -> t) -> t -> t
toList' = toList
toList :: [a] -> List a
toList = toList'
這可以解釋為什么sum
的類型無法檢查。 您可以通過將多態定義打包在newtype
包裝中來避免此類問題,以避免這種提升 (該段落不會出現在較新版本的doc中,因此我之前使用條件)。
{-# LANGUAGE RankNTypes #-}
import Prelude hiding (sum)
newtype List h = List { runList :: forall t . (h -> t -> t) -> t -> t }
sum_ :: (Num a) => List a -> a
sum_ xs = runList xs (+) 0
toList :: [a] -> List a
toList xs = List $ \ c n -> foldr c n xs
sum :: (Num a) => [a] -> a
sum = sum_ . toList
main = print (sum [1,2,3])
這是一個你可以嘗試的有點可怕的技巧。 你將擁有一個rank-2類型的變量,而是使用一個空類型; 在任何地方你都會選擇類型變量的實例化,使用unsafeCoerce
。 使用空類型可以確保(盡可能多)您不會執行任何可以觀察應該是不可觀察值的內容。 因此:
import Data.Void
import Unsafe.Coerce
type List a = (a -> Void -> Void) -> Void -> Void
toList :: [a] -> List a
toList xs = \cons nil -> foldr cons nil xs
sum_ :: Num a => List a -> a
sum_ xs = unsafeCoerce xs (+) 0
main :: IO ()
main = print (sum_ . toList $ [1,2,3])
你可能想寫一個稍微安全的unsafeCoerce
版本,比如:
instantiate :: List a -> (a -> r -> r) -> r -> r
instantiate = unsafeCoerce
然后sum_ xs = instantiate xs (+) 0
作為替代定義可以正常工作,並且您不會冒險將List a
變為任意的TRULY。
通常,等式推理僅存在於Haskell所代表的“基礎系統F”中。 在這種情況下,正如其他人所指出的,你要絆倒了,因為Haskell的移動forall
小號向左,並自動在不同的點采用了適當的類型。 你可以通過提供類型應用程序應該通過newtype
包裝器發生的位置來解決它。 正如你所見,你也可以通過eta擴展來操作類型應用程序,因為Hindley-Milner的輸入規則對於let
和lambda是不同的: forall
是通過“泛化”規則引入的,默認情況下是在let
s(和另外,相當於命名綁定)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.