10

我对 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, ...-

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

- 编辑 -

好的,所以让 order_list = CashOnDelivery:order_list 不会将 CashOnDelivery 添加到原始 order_list 然后将结果设置为 order_list,而是递归并创建一个无限列表,永远将 CashOnDelivery 添加到开头本身。当然,现在我记得 Haskell 是一种函数式语言,我无法更改原始 order_list 的值,那么对于简单的“将其添加到此列表的末尾(或开头,无论如何)我该怎么办?” 制作一个以列表和 BillingInfo 作为参数的函数,然后返回一个列表?

-- 编辑 2 --

好吧,基于我得到的所有答案以及无法通过引用和变异变量传递对象(例如我习惯的)......我想我只是过早地问了这个问题,而且我真的需要进一步研究函数范式,然后才能真正理解我的问题的答案......我想我正在寻找的是如何编写一个函数或其他东西,获取一个列表和一个项目,然后返回一个同名列表,因此可以多次调用该函数,而无需每次都更改名称(好像它实际上是一个将实际订单添加到订单列表的程序,用户不必考虑每次列表的新名称,而是将一个项目附加到同一个列表中)。

4

12 回答 12

15

你来做这件事:

$ 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]
于 2009-04-27T22:07:35.837 回答
7

作为一名正在康复的 ML 程序员,我一直被这个人抓住。这是 Haskell 中为数不多的烦恼之一,您不能轻易地在letorwhere子句中重新绑定名称。如果要使用letor where,则必须发明新名称。在顶层的 read-eval-print 循环中,如果你想一次绑定一个名字,你别无选择。但是如果你愿意嵌套构造,你可以滥用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 个无意义的变体.

于 2009-04-28T02:03:15.943 回答
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这并不是真的有用,但是还有许多其他地方能够使用无限(或非常大)数据结构进行编程非常方便。

于 2009-04-27T22:07:03.483 回答
2

是的,您正在尝试打印一个可以通过惰性评估创建的无限列表。例如

let a = 1 : a

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

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

于 2009-04-27T21:52:03.720 回答
1

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

编辑:阅读乔尔的回答后,我似乎在这里和一个 Lisp 说话。懒惰的评估与否,无论如何你已经创建了一个递归定义......

于 2009-04-27T21:43:48.140 回答
1

Haskell 使用惰性评估...在需要之前不会评估任何内容,这就是为什么 order_list 存储为包含 CashOnDelivery 和另一个未评估的单元格再次引用 order_list 的原因。

于 2009-04-27T21:44:46.267 回答
1

我认为这里的重要问题不是懒惰而是范围。该表达式let x = ...引入了一个新定义,x该定义将替换任何先前的定义。如果x出现在右侧,则定义将是递归的。看来您希望在order_list右侧使用

let order_list = CashOnDelivery : order_list

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

于 2009-04-27T22:10:38.560 回答
0

我认为您的意思是“这是因为它使用惰性评估”吗?答案是肯定的:

let ol = CashOnDelivery : ol

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

于 2009-04-27T21:46:55.573 回答
0

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

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

于 2009-04-27T21:57:28.950 回答
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中没有s。Invoice

于 2009-04-27T22:11:18.807 回答
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 中,所有顶级let、 和where绑定都是递归的——没有非递归绑定。

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

例外:在 中do<-绑定不是递归的。但是,如果您使用{-# LANGUAGE RecursiveDo #-}和 import 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, ...]
于 2009-04-28T15:56:13.757 回答
0

也许试试这个

我们创建了一个函数来执行此操作。(如在函数式编程中)

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

let f x = x : order_list~注意,每次我们追加 order_list 时,我们都需要重铸函数,以便将其固定到最新的 order_list

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

干杯!

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

于 2011-03-31T04:47:44.283 回答