【问题标题】:Does "<-" mean assigning a variable in Haskell?“<-”是否意味着在 Haskell 中分配一个变量?
【发布时间】:2016-05-13 22:33:03
【问题描述】:

刚开始Haskell,据说Haskell中的一切都是“不可变的”,除了IO包。因此,当我将名称绑定到某物时,它总是不可变的吗?问题,如下所示:

Prelude> let removeLower x=[c|c<-x, c `elem` ['A'..'Z']]
Prelude> removeLower "aseruiiUIUIdkf"
"UIUI"

所以这里:

1. “removeLower" is an immutable? Even it's a function object?
But I can still use "let" to assign something else to this name.

2. inside the function "c<-x" seems that "c" is a variable.
It is assigned by list x's values.

我使用的是 C 语言中的“变量”一词,不确定 Haskell 是如何命名所有名称的?

谢谢。

【问题讨论】:

  • 如果我看到它,我想取出一个值 ^^
  • "但我仍然可以使用 "let" 为这个名称分配其他内容。" - 确切地说,您可以分配给 name,而不是值。这与任何方式的突变都不对应。调用 c 变量很好 - 它的值确实不同。

标签: variables haskell immutability assign


【解决方案1】:

如果您熟悉 C,请考虑声明变量和分配值之间的区别。例如,您可以自己声明一个变量,然后再分配给它:

int i;
i = 7;

或者你可以同时声明一个变量并赋初值:

int i = 7;

在任何一种情况下,您都可以通过在第一次初始化或赋值后再次赋值来改变变量的值:

int i = 7;  // Declaration and initial assignment
i = 5;      // Mutation

Haskell 中的赋值只像第二个例子一样工作——带有初始化的声明:

  1. 你声明了一个变量;
  2. Haskell 不允许未初始化的变量,因此您需要在声明中提供值;
  3. 没有突变,因此声明中给出的值将是该变量在其 scope 中的唯一值。

我用粗体和超链接“范围”,因为它是这里的第二个关键组成部分。这是您的问题之一:

“removeLower”是不可变的?即使它是一个函数对象?但我仍然可以使用“let”为这个名称分配其他东西。

将removeLower 绑定到您在示例中定义的函数后,名称removeLower 将始终引用该函数在该定义的范围内。这很容易在解释器中演示。首先,我们定义一个函数foo:

Prelude> let foo x = x + 2
Prelude> foo 4
6

现在我们定义一个使用foo的bar:

Prelude> let bar x = foo (foo x)
Prelude> bar 4
8

现在我们将foo“重新定义”为不同的东西:

Prelude> let foo x = x + 3
Prelude> foo 4
7

现在你认为bar 会发生什么?

Prelude> bar 4
8

还是一样!因为foo 的“重新定义”并没有改变任何东西——它只是说,在“重新定义”创建的新范围内,名称foo代表加三的函数。 bar 的定义是在 foo x = x + 2 的较早范围内进行的,因此这就是名称 foo 在 bar 的定义中的含义。 foo 的原始值没有被“重新定义”破坏或改变。

在 Haskell 程序中和在 C 程序中一样,相同的名称仍然可以在程序的不同范围内引用不​​同的值。这就是使“变量”可变的原因。不同之处在于,在 Haskell 中,您永远不能在一个范围内改变变量的值。但是,您可以shadow 定义——变量的使用在某种意义上指的是该名称的“最近”定义。 (对于解释器,该变量的最新let 声明。)


现在,除此之外,这里是 Haskell 中存在的用于变量绑定(“赋值”)的语法。首先,模块中有顶级声明:

module MyLibrary (addTwo) where

addTwo :: Int -> Int
addTwo x = x + 2

这里名称addTwo 以给定函数作为其值声明。顶级声明可以在 where 块中包含私有的辅助声明:

addSquares :: Integer -> Integer
addSquares x y = squareOfX + squareOfY
  where square z = z * z
        squareOfX = square x
        squareOfY = square y

然后是let ... in ... 表达式,它允许您为任何表达式声明一个局部变量:

addSquares :: Integer -> Integer
addSquares x y = 
  let square z = z * z
      squareOfX = square x
      squareOfY = square y
  in squareOfX + squareOfY

然后是do-notation,它有自己的声明变量的语法:

example :: IO ()
example = do
  putStrLn "Enter your first name:"
  firstName <- getLine

  putStrLn "Enter your lasst name:"
  lastName <- getLine

  let fullName = firstName ++ " " ++ lastName
  putStrLn ("Hello, " ++ fullName ++ "!")

var &lt;- action 分配一个由动作 产生的值(例如,从标准输入读取一行),而let var = expr 分配一个由函数产生的值(例如,连接一些字符串)。请注意,do 块中的 let 与上面的 let ... in ... 不同!

最后,在列表推导中,您会得到与 do-notation 中相同的赋值语法。

【讨论】:

    【解决方案2】:

    它指的是一元绑定运算符&gt;&gt;=。您只是不需要将 lambda 显式编写为右侧参数。列表压缩将被编译为定义的一元动作。这意味着与一元环境中的完全相同。

    事实上,您可以通过简单地调用filter 来替换列表压缩:

    filter (`elem` ['A' .. 'Z']) x
    

    更清楚地回答您关于&lt;- 句法结构的问题:

    [c| c <- x]
    

    和

    一样
    do c <- x
       return c
    

    和

    一样
    x >>= \c -> return c
    

    和

    一样
    x >>= return
    

    参考 Haskell 的官方文档进一步阅读:https://hackage.haskell.org/package/base-4.8.2.0/docs/Control-Monad.html#v:-62--62--61-

    【讨论】:

    • IMO 最好写 [c | c &lt;- x ] = do c &lt;- x \n return c (在评论中我不能写换行符/缩进 - 但我希望你明白)
    • @Carsten 是的,我知道你的意思。我会改变它。但我不得不承认我不使用列表推导。
    【解决方案3】:
    [c|c<-x, c `elem` ['A'..'Z']]
    

    是 list comprehension,c &lt;- x 是生成器,其中 c 是要从列表 x 的元素中匹配的模式。 c 是一个模式,它连续绑定到输入列表 x 的元素,这些元素是 a、s、e、u,...当您评估 removeLower "aseruiiUIUIdkf" 时。

    c `elem` ['A'..'Z']
    

    是一个谓词,应用于理解中c 的每个连续绑定,并且输入的元素只有在通过此谓词时才会出现在输出列表中。

    【讨论】:

      猜你喜欢
      • 2017-12-19
      • 2015-03-12
      • 1970-01-01
      • 2021-10-01
      • 2011-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-01
      相关资源
      最近更新 更多