【问题标题】:in haskell, how to represent infinite data which is newly defined在haskell中,如何表示新定义的无限数据
【发布时间】:2015-02-04 19:14:44
【问题描述】:

我在 Haskell 中通过以下方式定义了一个新的数据类型:

data Pro = P Int Pro | Idle
              deriving Show

然后我定义了一个适用于这种新数据类型的运算符:

(>*>) :: Pro -> Pro -> Pro
Idle        >*>   ps  = ps
P i ps      >*>   qs  = P i (ps >*> qs)

所以,infi r = P r Idle >*> (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 的惰性属性似乎不适用于这种新数据类型。任何人都可以告诉我原因以及如何通过第二种数据类型打印无限数据。

【问题讨论】:

    标签: haskell types lazy-evaluation


    【解决方案1】:

    另一个答案是正确的,正确的解决方法是使用~-pattern(也称为无可辩驳的模式或惰性模式)或新类型,但让我们看看为什么。 ..

    以下是您的定义再次供参考:

    (>/>) :: 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 []。然而,第二种情况需要>/> 的第二个参数的形状为T qs,而我们有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
    

    >/> 定义的第二种情况下,我们仅在知道两个参数都是Ts 之后生成T。所以在infi2 r中,我们只能在infi2 r返回后减少外层>/>,但那是递归调用……

    现在关于解决此问题的解决方案:

    使用新类型

    newtype Try = T [Int]
      deriving (Show)
    

    而不是dataT 上的模式匹配变成了无操作。新类型保证与底层类型(此处为 [Int])具有相同的运行时表示,并且应用构造函数 T 或模式匹配对转换类型有影响,但在运行时没有影响。

    因此,一旦我们有

    T [1] >/> infi2 1
    

    为了对其中一个案例做出决定,我们现在只看到第一个列表是非空的,所以第一个案例不能适用。第二种情况是左侧

    T ts >/> T qs = ...
    

    T 上的模式匹配是一个 noop 的假设下,这是微不足道的,可以立即减少。

    使用~-模式

    同样,如果我们继续使用data,但是写

    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)
    

    这表明我们对第二个参数没有任何期望 在模式匹配时。这个版本非常符合~-pattern-version 将编译成的版本。

    总结一下,现在让我们看看减少:

       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)))
    

    此时,应该很明显,我们确实是增量获得了无限列表。

    【讨论】:

      【解决方案2】:

      你需要tilda:

      (>/>) :: Try -> Try -> Try
      T []    >/>     i       = i 
      T ts    >/>     ~(T qs)    = T (ts ++ qs )
      

      另外你不需要第一个子句,所以(>/>)可以定义为

      (>/>) :: Try -> Try -> Try
      ~(T ts) >/> ~(T qs) = T (ts ++ qs)
      

      (>*>) 的定义是惰性的,因为第二个参数没有模式匹配。

      更新

      正如@MigMit 所建议的,您可以只使用newtype,并且您对(>/>) 的原始定义将起作用。看看https://wiki.haskell.org/Newtype2 The messy bits部分

      【讨论】:

      • 我建议使用newtype 而不是data
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-26
      • 2018-09-10
      • 2012-03-22
      • 2017-04-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多