Haskell Cons运算符(:)

时间:2009-04-27 21:39:49

标签: haskell functional-programming operators

我真的是Haskell的新手(实际上我从O'Reilly看到了“Real World Haskell”并且认为“嗯,我想我昨天将学习函数式编程”)我想知道:我可以使用构造运算符来将项添加到列表的开头:

1 : [2,3]
[1,2,3]

我尝试制作一本我在书中找到的示例数据类型然后再玩它:

--in a file
data BillingInfo = CreditCard Int String String
| CashOnDelivery
| Invoice Int
deriving (Show)

--in ghci
 $ let order_list = [Invoice 2345]
 $ order_list
[Invoice 2345]
 $ let order_list = CashOnDelivery : order_list
 $ order_list
[CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, ...-

等......它只是永远重复,这是因为它使用 lazy 评估吗?

- 编辑 -

好的,所以让我的脑袋里面有一个让order_list = CashOnDelivery:order_list不会将CashOnDelivery添加到原始order_list然后将结果设置为order_list,而是递归并创建一个无限列表,永远添加CashOnDelivery到了自己的开始。当然,现在我记得Haskell是一种函数式语言,我不能改变原始order_list的值,所以我应该怎样做一个简单的“把这个添加到这个列表的最后(或者开始,等等)?” 创建一个以列表和BillingInfo为参数的函数,然后返回一个列表?

- 编辑2 -

好吧,基于我得到的所有答案以及缺乏能够通过引用传递对象和变异变量(例如我习惯)......我想我刚刚问过这个问题过早地,我真的需要深入研究功能范例,才能真正理解我的问题的答案...我想我想要的是如何写一个函数或什么,拿一个列表和一个项目,并返回一个相同名称的列表,这样可以多次调用该函数,而不是每次都更改名称(就好像它实际上是一个将实际订单添加到订单列表的程序,并且用户不会每次都要考虑列表的新名称,而是将项目附加到同一列表中)。

12 个答案:

答案 0 :(得分:14)

你这样做:

$ let order_list = [Invoice 2345]
$ let order_list = CashOnDelivery : order_list

这里需要注意的重要一点是,您只是在第一个CashOnDelivery添加了order_list项。您正在定义一个与第一个变量无关的新变量order_list。这是递归定义,右侧的order_list是指您在左侧定义的order_list,而不是前一行中定义的$ let order_list = [Invoice 2345] $ order_list [Invoice 2345] $ let order_list2 = CashOnDelivery : order_list $ order_list2 [CashOnDelivery, Invoice 2345] 。由于这种递归,你会得到一个无限的列表。

我怀疑你真的想做这样的事情:

{{1}}

答案 1 :(得分:6)

作为一名正在恢复的ML程序员,我一直被这个抓住。这是Haskell中为数不多的烦恼之一,你不能轻易地在letwhere条款中重新绑定名称。如果您想使用letwhere,则必须创建新名称。在顶级的read-eval-print循环中,如果要一次绑定一个名称,则别无选择。但是,如果您愿意嵌套构造,则可以使用身份monad滥用do表示法:

import Control.Monad.Identity

let order_list = runIdentity $ do
       order_list <- return [Invoice 2345]
       order_list <- return $ CashOnDelivery : order_list
       return order_list

是的,这段代码是卑鄙的 - 而且这个例子不值得 - 但如果我有一长串的重新绑定,我可能会诉诸它,所以我不必发明5或6个无意义的变化同名。

答案 2 :(得分:3)

回答问题编辑:

  

那么我应该怎样做一个简单的“把这个放到这个列表的最后(或开始,无论如何)?”创建一个以列表和BillingInfo为参数的函数,然后返回一个列表?

啊,但是已经有一个“函数”用于将元素添加到列表中:它是cons (:)构造函数: - )

因此,只要不对两个不同的变量使用相同的名称,您的现有代码就可以正常工作,因为第二个名称绑定将遮蔽(隐藏)第一个。

ghci> let first_order_list = [Invoice 2345]
ghci> first_order_list
[Invoice 2345]
ghci> let second_order_list = CashOnDelivery : first_order_list
ghci> second_order_list
[CashOnDelivery, Invoice 2345]

关于第二次修改:

既然你在实际计划中询问如何做这样的事情,我会说:

如果您反复向列表中添加内容,则不希望每次都为该列表创建新名称。但是这里的关键字是“重复”的,在命令式编程中你会在这里使用一个循环(并在循环中修改一些变量)。

因为这是函数式编程,所以不能使用循环,但可以使用递归。以下是我编写允许用户输入订单并收集列表的程序的方法:

main = do
  orderList <- collectBillingInfos
  putStrLn ("You entered these billing infos:\n" ++ show orderList)

collectBillingInfos :: IO [BillingInfo]
collectBillingInfos = loop []
  where
    loop xs = do
      putStrLn "Enter billing info (or quit)"
      line <- getLine
      if line /= "quit"
        then loop (parseBillingInfo line : xs)
        else return xs

parseBillingInfo :: String -> BillingInfo
parseBillingInfo _ = CashOnDelivery -- Don't want to write a parser here

回顾一下; loop函数递归调用自身,每次都有一个新元素添加到列表中。在用户输入“退出”之前,它会停止调用自身并返回最终列表。


与懒惰评估相关的原始答案:

正如其他人所说,这是一个递归定义,使order_list成为仅包含CashOnDelivery值的无限列表。 虽然懒惰的评估不是它的原因,但它确实有用。

由于懒惰的评估,您可以像这样使用order_list

ghci> take 3 order_list
[CashOnDelivery, CashOnDelivery, CashOnDelivery]

如果您没有延迟评估,则对take的调用会崩溃,因为它会首先尝试评估order_list(这是无限的)。

现在,对于order_list来说,这并不是很有用,但是还有很多其他地方能够使用无限(或非常大)的数据结构进行编程非常方便。

答案 3 :(得分:2)

是的,您正在尝试打印一个可以使用延迟评估创建的无限列表。例如

let a = 1 : a

创建无限的列表,您可以使用take函数或尝试打印时尽可能多地使用它们。请注意,您在等式的左侧和右侧使用相同的标识符,并且它有效:order_list是CashOnDelivery:order_list,现在替换:order_list = CashOnDelivery:(CashOnDelivery:order_list)= Cash ... etc。

如果您想创建[Cash ...,Invoice]列表,请不要重复使用此类名称。

答案 4 :(得分:1)

您刚刚创建的缺点的cdr指向自身:第二个order_list中使用的let的定义是正在创建的定义。使用不同的变量名来完全回避递归问题,代码也不会那么混乱。

编辑:在阅读了Joel的回答之后,我似乎在这里和Lisp说话。懒惰评估或否,在任何情况下你都创建了一个递归定义......

答案 5 :(得分:1)

Haskell使用延迟评估...在需要之前不会对任何内容进行评估,这就是为什么order_list被存储为包含CashOnDelivery的cons和另一个未经评估的单元再次引用order_list。

答案 6 :(得分:1)

我认为这里的重要问题不是 laziness ,而是范围。表达式let x = ...引入了x的新定义,它将取代之前的任何定义。如果x出现在右侧,则定义将是递归的。您似乎期望在

的右侧使用order_list
let order_list = CashOnDelivery : order_list

引用order_list的第一个定义(即[Invoice 2345])。但是let表达式引入了一个新范围。相反,您已经定义了CashOnDelivery元素的无限列表。

答案 7 :(得分:0)

我认为你的意思是“这是因为它使用 lazy 评估”?答案是肯定的:

let ol = CashOnDelivery : ol

这告诉我们ol包含元素CashOnDelivery,然后是表达式ol的结果。 此表达式在必要时才会被评估(因此:懒惰)。因此,当打印ol时,将首先打印CashOnDelivery。 只有才能确定列表的下一个元素,从而导致无限的行为。

答案 8 :(得分:0)

X:L表示“创建一个以X开头的列表,后跟L定义的列表中的项目。”

然后,您将order_list定义为CashOnDelivery,后跟列表中定义的order_list中的项目。这个定义是递归的,这就是列表评估不断返回CashOnDelivery的原因。您的列表实际上包含一个无限数量的CashOnDelivery值跟随一个发票值。

答案 9 :(得分:0)

order_list在这一行:

let order_list = [Invoice 2345]

是此行中order_list的另一个变量

let order_list = CashOnDelivery : order_list

第二行不会更改order_list的值。它引入了一个名称相同但值不同的新变量。

第二行右侧的

order_list与第二行左侧的order_list相同;它与第一行中的order_list无关。你得到一个充满CashOnDelivery的无限列表---第二个列表中没有Invoice

答案 10 :(得分:0)

在ML中,val不是递归的。您必须为递归值指定val rec

val fin = fn _ => 0
val fin = fn x => fin x + 1
(* the second `fin` is calling the first `fin` *)

val rec inf = fn x => inf x + 1
(* ML must be explicitly told to allow recursion... *)
fun inf' x = inf' x + 1
(* though `fun` is a shortcut to define possibly recursive functions *)

在Haskell中,所有顶级letwhere绑定都是递归的 - 没有非递归绑定。

let inf = \_ -> 0
let inf = \x -> inf x + 1
-- the second `inf` completely shadows the first `inf`

例外:在do中,<-绑定不是递归的。但是,如果您使用{-# LANGUAGE RecursiveDo #-}并导入Control.Monad.Fix,则会获得mdo,其中<-绑定是递归的。

foo :: Maybe [Int]
foo = do
    x <- return [1]
    x <- return (0 : x)  -- rhs `x` refers to previous `x`
    return x
-- foo == Just [0, 1]

bar :: Maybe [Int]
bar = mdo
    y <- return (0 : y)  -- rhs `x` refers to lhs `x`
    return y
-- bar == Just [0, 0, 0, ...]

答案 11 :(得分:0)

也许试试这个

我们制作功能来执行此操作。(如 fun ctional编程)

$ let order_list = [Invoice 2345]
$ let f x = x : order_list
$ let order_List = f cashOnDelivery
$ order_list
[CashOnDelivery, [Invoice 2345]]

〜注意我们每次附加order_list时都需要重新设置函数let f x = x : order_list,以便我们将它固定到最新的order_list

$ let f x = x : order_list
$ let order_List = f cashOnDelivery
$ order_list
[CashOnDelivery, CashOnDelivery, [Invoice 2345]]

干杯!

ps,函数式编程的强大功能是能够无缝地在异步(并行/超快)系统上运行无限量的函数和对象,因为所有的func和obj都是独立的。