【问题标题】:Haskell Cons Operator (:)Haskell 缺点运算符 (:)
【发布时间】:2009-04-27 21:39:49
【问题描述】:

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

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

【问题讨论】:

  • 您将始终创建一个新列表。为什么要使用旧名称来指代新列表?
  • 在你能理解答案之前,你必须先理解你的问题。

标签: haskell functional-programming operators


【解决方案1】:

你这样做:

$ 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]

【讨论】:

    【解决方案2】:

    作为一名正在恢复中的 ML 程序员,我一直被这个问题所吸引。这是 Haskell 中为数不多的烦恼之一,您不能轻易地在 letwhere 子句中重新绑定名称。如果你想使用letwhere,你必须发明新的名字。在顶层的 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 个无意义的变体同名。

    【讨论】:

    • 嘿,我只会得到 order_list、order_list'、order_list''、order_list''' 等......
    • 呵呵,我有时在 IO monad 中这样做,给我一种很好的命令感 ;-)
    【解决方案3】:

    回答问题编辑:

    那么对于简单的“将其添加到此列表的末尾(或开头,等等)”,我应该怎么做?制作一个以列表和 BillingInfo 作为参数的函数,然后返回一个列表?

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

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

    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]
    

    关于第二次编辑:

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

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

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

    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 函数递归调用自身,每次都会在列表中添加一个新元素。直到用户输入“quit”,它才会停止调用自己并返回最终列表。


    关于惰性评估的原始答案:

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

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

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

    如果您没有惰性求值,对take 的调用会崩溃,因为它会首先尝试求值order_list(这是无限的)。

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

    【讨论】:

      【解决方案4】:

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

      let a = 1 : a
      

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

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

      【讨论】:

        【解决方案5】:

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

        编辑:在阅读了 Joel 的回答后,我似乎在这里与 Lisp 交谈。惰性求值与否,无论如何你已经创建了一个递归定义......

        【讨论】:

          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

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

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

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

            【讨论】:

              【解决方案8】:

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

              let ol = CashOnDelivery : ol
              

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

              【讨论】:

                【解决方案9】:

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

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

                【讨论】:

                  【解决方案10】:

                  order_list 在这一行:

                  let order_list = [Invoice 2345]
                  

                  是与此行中order_list 不同的变量

                  let order_list = CashOnDelivery : order_list
                  

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

                  第二行右侧的order_list 与第二行左侧的order_list 相同;它与第一行中的order_list 无关。您会得到一个无限列表,其中包含 CashOnDelivery --- 第二个列表中没有 Invoices。

                  【讨论】:

                    【解决方案11】:

                    在 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 中,&lt;- 绑定不是递归的。但是,如果使用{-# LANGUAGE RecursiveDo #-} 并导入Control.Monad.Fix,则会得到mdo,其中&lt;- 绑定是递归的。

                    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, ...]
                    

                    【讨论】:

                      【解决方案12】:

                      不妨试试这个

                      我们制作了一个函数来做到这一点。(如fun函数式编程)

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

                      【讨论】:

                        猜你喜欢
                        • 2011-02-19
                        • 2014-09-08
                        • 2012-01-25
                        • 2016-12-21
                        • 1970-01-01
                        • 1970-01-01
                        • 2021-10-28
                        • 1970-01-01
                        • 2011-05-04
                        相关资源
                        最近更新 更多