在haskell中,如何表示新定义的无限数据
我用以下方式在Haskell中定义了一个新的数据类型:在haskell中,如何表示新定义的无限数据,haskell,types,lazy-evaluation,Haskell,Types,Lazy Evaluation,我用以下方式在Haskell中定义了一个新的数据类型: data Pro = P Int Pro | Idle deriving Show 然后,我定义了一个适用于此新数据类型的运算符: (>*>) :: Pro -> Pro -> Pro Idle >*> ps = ps P i ps >*> qs = P i (ps >*> qs) 因此,infir=pridle>
data Pro = P Int Pro | Idle
deriving Show
然后,我定义了一个适用于此新数据类型的运算符:
(>*>) :: Pro -> Pro -> Pro
Idle >*> ps = ps
P i ps >*> qs = P i (ps >*> qs)
因此,infir=pridle>*>(infi(r+1))
可以表示无限数据,如果我在终端中键入infi4 1,它将无限打印
后来,我意识到这个新的数据类型定义可以修改为:
data Try = T [Int]
deriving Show
它们非常相似,后者是一种列表,似乎更简单。
但当我定义以下无限数据时:
(>/>) :: Try -> Try -> Try
T [] >/> i = i
T ts >/> T qs = T (ts ++ qs )
infi2 r = (T [r]) >/> (infi2 r)
并试图在终端上打印,结果显示:
Exception: stack overflow
Haskell的lazy属性似乎无法在这种新数据类型上工作。是否有人可以告诉我原因以及如何按第二种数据类型打印无限数据。您需要:
另外,您不需要第一个子句,因此(>/>)
可以定义为
(>/>) :: Try -> Try -> Try
~(T ts) >/> ~(T qs) = T (ts ++ qs)
(>*>)
的定义是惰性的,因为在第二个参数上没有模式匹配
更新
正如@MigMit所建议的,您只需使用
newtype
,您对(>/>)
的原始定义就可以了。请看一下2的凌乱部分另一个答案是正确的,即正确的修复方法是使用~
-模式(也称为无可辩驳的模式或惰性模式)或新类型,但让我们看看为什么
以下是您的定义供参考:
(>/>) :: Try -> Try -> Try
T [] >/> i = i
T ts >/> T qs = T (ts ++ qs)
infi2 r = T [r] >/> infi2 r -- I've omitted unnecessary parentheses
现在,如果调用infi2 1
,将发生以下减少:
infi2 1
= { expanding the definition of infi2 }
T [1] >/> infi2 1
这是重点。我们想减少最外层的功能,
这是/>
。但我们必须决定哪种情况适用。这很容易
查看第一个不匹配,因为T[1]
与T[]
不匹配。然而,第二种情况要求/>
的第二个参数的形状为tqs
,我们有infi2 1
。即使Try
只有一个构造函数,GHC/Haskell也不会做出这样的飞跃。相反,它将进一步计算infi2 1的。因此,下一个缩减步骤是
T [1] >/> infi2 1
= { expanding the definition of infi2 }
T [1] >/> (T [1] >/> infi2 1)
现在我们又处于完全相同的情况。我们仍然不能减少最外层的/>
,因为我们不知道正确参数的构造函数;因此,我们必须进一步降低这一比例。但在这方面,我们需要再次降低成本
右参数进一步了解内部/>
右参数的构造函数:
T [1] >/> (T [1] >/> infi2 1)
= { expanding the definition of infi2 }
T [1] >/> (T [1] >/> infi2 1)
= { expanding the definition of infi2 }
T [1] >/> (T [1] >/> (T [1] >/> infi2 1))
= ...
这将无限期地继续,直到内存满为止。我们永远也做不到
真正的进步
再次回顾原始定义,(通过一点实践)实际上可以看到这一点,而无需进行整个扩展:
(>/>) :: Try -> Try -> Try
T [] >/> i = i
T ts >/> T qs = T (ts ++ qs)
infi2 r = T [r] >/> infi2 r
在/>
定义的第二种情况下,只有在我们知道两个参数都是T
s之后,我们才产生T
。因此在infi2r
中,我们只能在infi2r
返回后减少外部/>
,但这是一个递归调用
现在介绍解决此问题的解决方案:
使用新类型
与
在T
上的模式匹配不再是data
,而是成为禁止操作。新类型保证具有与基础类型相同的运行时表示形式(此处为[Int]
),并且应用构造函数T
或模式匹配对转换类型有影响,但在运行时没有影响
因此,一旦我们
T [1] >/> infi2 1
为了对其中一个案例做出决定,我们现在只看到第一个列表是非空的,因此第一个案例不适用。第二例为左侧
T ts >/> T qs = ...
在假设T
上的模式匹配是一个noop的情况下,这是非常正确的,并且可以立即减少
使用~
-模式
类似地,如果我们继续使用数据
,但是
T ts >/> ~(T qs) = ...
我们改变了GHC/Haskell的行为,以实现我上面提到的“信仰的飞跃”。无可辩驳的模式匹配会自动成功,因此不会引起进一步的评估。对于单个构造函数数据类型,例如Try
,这样做基本上是安全的。但是,如果在多构造函数数据类型上进行这种惰性模式匹配,结果发现匹配的值不是模式中出现的构造函数的值,那么匹配仍然会成功,并且一旦尝试使用模式内部的值,就会出现运行时异常
显式提取
第三种选择是编写提取函数
unT :: Try -> [Int]
unT (T ts) = ts
然后说
(>/>) :: Try -> Try -> Try
T [] >/> i = i
T ts >/> qs = T (ts ++ unT qs)
这表明我们对第二个论点没有任何期望
在模式匹配时。这个版本非常符合~
-模式版本将编译到的内容
最后,让我们看看现在的降价情况:
infi2 1
= { expanding the definition of infi2 }
T [1] >/> infi2 1
= { expanding the definition of >/> }
T ([1] ++ unT (infi2 1))
假设我们要打印结果并进行完全还原,让我们从这里继续一段时间:
T ([1] ++ unT (infi2 1))
= { expanding the definition of ++ }
T (1 : unT (infi2 1))
= { expanding the definition of infi2 }
T (1 : unT (T [1] >/> infi2 1))
= { expanding the definition of >/> }
T (1 : unT (T ([1] ++ unT (infi2 1))))
= { expanding the definition of the outer unT }
T (1 : ([1] ++ unT (infi2 1)))
在这一点上,很明显我们确实是以递增的方式获得无限列表。我建议使用newtype
而不是数据。
infi2 1
= { expanding the definition of infi2 }
T [1] >/> infi2 1
= { expanding the definition of >/> }
T ([1] ++ unT (infi2 1))
T ([1] ++ unT (infi2 1))
= { expanding the definition of ++ }
T (1 : unT (infi2 1))
= { expanding the definition of infi2 }
T (1 : unT (T [1] >/> infi2 1))
= { expanding the definition of >/> }
T (1 : unT (T ([1] ++ unT (infi2 1))))
= { expanding the definition of the outer unT }
T (1 : ([1] ++ unT (infi2 1)))