我真的是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 -
好吧,基于我得到的所有答案以及缺乏能够通过引用传递对象和变异变量(例如我习惯)......我想我刚刚问过这个问题过早地,我真的需要深入研究功能范例,才能真正理解我的问题的答案...我想我想要的是如何写一个函数或什么,拿一个列表和一个项目,并返回一个相同名称的列表,这样可以多次调用该函数,而不是每次都更改名称(就好像它实际上是一个将实际订单添加到订单列表的程序,并且用户不会每次都要考虑列表的新名称,而是将项目附加到同一列表中)。答案 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中为数不多的烦恼之一,你不能轻易地在let
或where
条款中重新绑定名称。如果您想使用let
或where
,则必须创建新名称。在顶级的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
的定义是正在创建的定义。使用不同的变量名来完全回避递归问题,代码也不会那么混乱。
答案 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中,所有顶级let
和where
绑定都是递归的 - 没有非递归绑定。
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都是独立的。