如何定义仍具有递归类型

时间:2017-09-04 12:00:27

标签: haskell types recursive-type

给出的是像

这样的Javascript函数
const isNull = x => x === null ? [] : [isNull];

这样的功能可能是无稽之谈,但这不是问题。

当我尝试表达类似Haskell的类型注释时,我失败了。同样尝试在Haskell中实现类似的功能:

let isZero = \n -> if n == 0 then [] else [isZero] -- doesn't compile

这种函数是否有一个术语本身不递归,但在它们的类型中是递归的?这些函数只能用动态类型语言表达吗?

很抱歉,如果这很明显 - 我的Haskell知识(包括严格的类型系统)是相当肤浅的。

2 个答案:

答案 0 :(得分:5)

您需要为此定义一个显式递归类型。

newtype T = T (Int -> [T])

isZero :: T
isZero = T (\n -> if n == 0 then [] else [isZero])

支付的价格是T构造函数的包装/展开,但这是可行的。

如果你想模仿类似Javascript的无类型世界(AKA统一或动态输入),你甚至可以使用

data Value
   = VInt Int
   | VList [Value]
   | VFun (Value -> Value)
   ...

(谨防known bug

原则上,每个Javascript值都可以用上面的巨大和类型表示。例如,应用程序就像

jsApply (VFun f) v = f v
jsApply _        _ = error "Can not apply a non-function value"

注意静态类型检查如何以这种方式转换为动态检查。同样,静态类型错误也会变成运行时错误。

答案 1 :(得分:3)

Chi展示了如何实现这种无限类型:你需要一个newtype包装器来“隐藏”无限递归。

一个有趣的选择是使用fixpoint formulation。回想一下,你可以伪造一些像你的例子一样递归的东西

isZero = fix $ \f n -> if n == 0 then [] else [f]

同样,类型实际上可以表示为相关仿函数的fixpoint,即(Int ->)[]的组合(在变换器中)格式塔是ListT):

isZero :: Fix (ListT ((->) Int))
isZero = Fix . ListT $ \n -> if n==0 then [] else [isZero]

另外值得注意的是,您可能并不真正想要ListT。如果您只有零个或一个元素,那么MaybeT会更自然。更妙的是,您可以使用仿函数修复点为closely related to the free monad这一事实,它可以为您提供“possibly trivial” alternative case

isZero' :: Free ((->) Int) ()
isZero' = wrap $ \n -> if n==0 then Pure () else isZero'
monad实例中的

Pure ()仅为return (),因此您也可以使用标准when替换if构造:

isZero' = wrap $ \n -> when (n/=0) isZero'