“<-” 是否表示在 Haskell 中分配变量?

vik*_*ata 6 variables haskell immutability assign

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

Prelude> let removeLower x=[c|c<-x, c `elem` ['A'..'Z']]
Prelude> removeLower "aseruiiUIUIdkf"
"UIUI"
Run Code Online (Sandbox Code Playgroud)

所以在这里:

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.
Run Code Online (Sandbox Code Playgroud)

我使用的是 C 语言中的“变量”这个词,不知道 Haskell 是如何命名它的所有名称的?

谢谢。

Lui*_*las 8

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

int i;
i = 7;
Run Code Online (Sandbox Code Playgroud)

或者您可以同时声明一个变量并分配初始值:

int i = 7;
Run Code Online (Sandbox Code Playgroud)

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

int i = 7;  // Declaration and initial assignment
i = 5;      // Mutation
Run Code Online (Sandbox Code Playgroud)

Haskell 中的赋值完全像第二个例子一样——带初始化的声明:

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

我加粗并超链接“范围”,因为它是这里的第二个关键组件。这是您的问题之一:

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

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

Prelude> let foo x = x + 2
Prelude> foo 4
6
Run Code Online (Sandbox Code Playgroud)

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

Prelude> let bar x = foo (foo x)
Prelude> bar 4
8
Run Code Online (Sandbox Code Playgroud)

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

Prelude> let foo x = x + 3
Prelude> foo 4
7
Run Code Online (Sandbox Code Playgroud)

现在你认为会发生什么bar

Prelude> bar 4
8
Run Code Online (Sandbox Code Playgroud)

它保持不变!因为“重定义”foo不会改变任何东西——它只是说,在由“重定义”创建的新作用域中,名称foo代表添加三的函数。的定义bar是在更早的范围 where 中进行的foo x = x + 2,所以这就是名称foo在 的定义中的含义bar。的原始值foo没有被“重新定义”破坏或改变。

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


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

module MyLibrary (addTwo) where

addTwo :: Int -> Int
addTwo x = x + 2
Run Code Online (Sandbox Code Playgroud)

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

addSquares :: Integer -> Integer
addSquares x y = squareOfX + squareOfY
  where square z = z * z
        squareOfX = square x
        squareOfY = square y
Run Code Online (Sandbox Code Playgroud)

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

addSquares :: Integer -> Integer
addSquares x y = 
  let square z = z * z
      squareOfX = square x
      squareOfY = square y
  in squareOfX + squareOfY
Run Code Online (Sandbox Code Playgroud)

然后是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 ++ "!")
Run Code Online (Sandbox Code Playgroud)

所述var <- action分配是由一个所产生的值的动作(例如,读取来自标准输入的线),而let var = expr受让人,其由产生的值函数(例如,串联一些字符串)。需要注意的是,let在一个do块是不是同样的事情let ... in ...上面!

最后,在列表理解中,您将获得与do-notation 中相同的赋值语法。


bas*_*h0r 4

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

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

filter (`elem` ['A' .. 'Z']) x
Run Code Online (Sandbox Code Playgroud)

<-为了更清楚地回答您有关语法结构的问题:

[c| c <- x]
Run Code Online (Sandbox Code Playgroud)

是相同的

do c <- x
   return c
Run Code Online (Sandbox Code Playgroud)

是相同的

x >>= \c -> return c
Run Code Online (Sandbox Code Playgroud)

是相同的

x >>= return
Run Code Online (Sandbox Code Playgroud)

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