“< - ”用符号表示绑定

时间:2012-03-29 20:21:42

标签: haskell binding

我很难理解这一点。用do notation编写时,以下两行有何不同?

1. let x = expression
2. x <- expression

我看不到它。有时一个工作,有时一个工作。但两者都很少。 “了解一个哈斯克尔”说<-将右侧与左侧的符号绑定。但是,与仅使用x定义let

有何不同?

6 个答案:

答案 0 :(得分:25)

<-语句将从monad中提取值,而let语句则不会。

import Data.Typeable

readInt :: String -> IO Int
readInt s = do
  putStrLn $ "Enter value for " ++ s ++ ": "
  readLn

main = do
  x <- readInt "x"
  let y = readInt "y"
  putStrLn $ "x :: " ++ show (typeOf x)
  putStrLn $ "y :: " ++ show (typeOf y)

运行时,程序将询问x的值,因为readInt "x"语句执行monadic操作<-。它不会要求y的值,因为readInt "y"被计算但是没有执行生成的monadic动作。

Enter value for x: 
123
x :: Int
y :: IO Int

x :: Int以来,你可以用它做正常的Int事。

putStrLn $ "x = " ++ show x
putStrLn $ "x * 2 = " ++ show (x * 2)

y :: IO Int以来,您无法假装它是常规Int

putStrLn $ "y = " ++ show y -- ERROR
putStrLn $ "y * 2 = " ++ show (y * 2) -- ERROR

答案 1 :(得分:13)

let绑定中,表达式可以包含任何类型,而您所做的只是为其命名(或在其内部结构上匹配模式)。

<-版本中,表达式的类型必须为m a,其中mdo块所在的monad。所以在IO中例如,monad,此表单的绑定必须在右侧具有某种类型IO a的值。 a部分(monadic值内)是左侧模式的约束。这使您可以在do块的有限范围内提取monad的“内容”。

正如您可能已经读过的那样,do符号只是monadic绑定运算符(>>=>>)的语法糖。 x <- expressionexpression >>= \x ->expression(本身,没有<-)去糖去expression >>去糖。这只是为定义monadic计算的长链提供了一种更方便的语法,否则它往往会构建一个相当令人印象深刻的嵌套lambda。

let绑定根本不会脱糖,真的。 let块中的dolet块之外的do之间的唯一区别是do版本不需要in }关键字跟随它;它绑定的名称隐含在do块的其余部分的范围内。

答案 2 :(得分:9)

let表单中,expression是非monadic值,而<-的右侧是monadic表达式。例如,您只能在第二种绑定中进行I / O操作(类型为IO t)。详细地说,这两种形式可以大致翻译为(其中==>显示翻译):

do {let x = expression; rest} ==> let x = expression in do {rest}

do {x <- operation; rest} ==> operation >>= (\ x -> do {rest})

答案 3 :(得分:3)

let只为任意值指定名称或模式匹配。

对于<-,让我们先离开(不是真的)神秘的IO monad,但要考虑具有“容器”概念的monad,如列表或{{1} }。然后Maybe只能“解包”该容器的元素。 “放回去”的相反操作是<-。请考虑以下代码:

return

它“解包”两个容器的元素,将值一起添加,并将它再次包装在同一个monad中。它适用于列表,采用所有可能的元素组合:

add m1 m2 = do
   v1 <- m1
   v2 <- m2
   return (v1 + v2) 

事实上,如果是列表,你也可以写main = print $ add [1, 2, 3] [40, 50] --[41,51,42,52,43,53] 。但我们的版本也适用于add m1 m2 = [v1 + v2 | v1 <- m1, v2 <- m2] s:

Maybe

现在main = print $ add (Just 3) (Just 12) --Just 15 main = print $ add (Just 3) Nothing --Nothing 并没有那么不同。它是单个值的容器,但它是像病毒一样的“危险”不纯的值,我们不能直接触摸它。 IO - 块在这里是我们的玻璃收容所,do是内置的“手套”来操纵里面的东西。使用<-,当我们准备好时,我们会提供完整,完整的容器(而不仅仅是危险内容)。顺便说一下,return函数也可以使用add值(我们从文件或命令行或随机生成器获得...)。

答案 4 :(得分:2)

Haskell通过使用形式IO a的类型表示命令行为来协调副作用命令式编程与纯函数式编程:产生a类型结果的命令行为的类型。

其中一个后果是将变量绑定到表达式的值并将其绑定到执行操作的结果是两回事:

x <- action        -- execute action and bind x to the result; may cause effect
let x = expression -- bind x to the value of the expression; no side effects

所以getLine :: IO String是一个动作,这意味着它必须像这样使用:

do line <- getLine -- side effect: read from stdin
   -- ...do stuff with line

line1 ++ line2 :: String是纯表达式,必须与let一起使用:

do line1 <- getLine            -- executes an action
   line2 <- getLine            -- executes an action
   let joined = line1 ++ line2 -- pure calculation; no action is executed
   return joined

答案 5 :(得分:2)

这是一个显示差异的简单示例。 考虑以下两个简单表达式:

letExpression = 2
bindExpression = Just 2

您要检索的信息是数字2。 这是你如何做到的:

let x = letExpression
x <- bindExpression

let直接将值2放入x<-2中提取值Just并将其放入x

您可以看到这个例子,为什么这两个符号不可互换:

let x = bindExpression会直接将值Just 2放入xx <- letExpression无法提取和放入x