为了刷新我20岁的Haskell经历,我正在浏览https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Adding_Variables_and_Assignment,并且在某一时刻引入了以下行,以便将op
应用于所有参数。这是为了实现例如(+ 1 2 3 4)
numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op
我不懂语法,文中的解释有点模糊。
我理解foldl1
的作用以及如何点函数(unpackNum
是辅助函数),但使用Monads和>>=
运算符让我有点困惑。
如何阅读?
答案 0 :(得分:8)
基本上,
df = pd.DataFrame({'address': ['NY', 'CA', 'NJ', 'NY', 'WS', 'OR', 'OR'],
'name1': ['john', 'mayer', 'dylan', 'bob', 'mary', 'jake', 'rob'],
'name2': ['mayer', 'dylan', 'mayer', 'bob', 'bob', 'tim', 'ben'],
'comment': ['n/a', 'n/a', 'n/a', 'n/a', 'n/a', 'n/a', 'n/a'],
'score': [90, 8, 88, 72, 34, 95, 50],
'selection_status': ['inprogress', 'inprogress', 'inprogress', 'inprogress', 'inprogress', 'inprogress', 'inprogress']})
由两部分组成。
mapM unpackNum params >>= return . Number . foldl1 op
表示:获取参数列表mapM unpackNum params
。在每个项目上,应用params
:这会在unpackNum
monad中生成一个Integer
。所以,它不是一个简单的ThrowsError
,因为它有机会出错。无论如何,在每个项目上使用Integer
成功生成所有unpackNum
,或者抛出一些错误。在第一种情况下,我们生成一个类型为Integers
的新列表,在第二种情况下,我们(不出所料)抛出错误。因此,此部分的结果类型为[Integer]
。
第二部分是ThrowsError [Integer]
。这里... >>= return . Number . foldl1 op
表示:如果第一部分抛出一些错误,整个表达式也会抛出该错误。如果部件成功生成>>=
,则继续[Integer]
,将结果包装为foldl1 op
,最后使用Number
将此值作为成功计算注入。
总的来说,有monadic计算,但你不应该考虑那些太多。这里的monadic东西只传播错误,或者如果计算成功则存储普通值。有了一点经验,人们可以专注于成功的值,让return
处理错误案例。
顺便说一句,请注意虽然本书使用的代码如mapM,>>=,return
,但这可能是一种糟糕的风格。人们可以使用相同的效果action >>= return . f
或fmap f action
,这是表达相同计算的更直接的方式。 E.g。
f <$> action
非常接近忽略错误情况的非monadic代码
Number . foldl1 op <$> mapM unpackNum params
答案 1 :(得分:5)
您的问题是关于语法的,所以我将谈谈如何解析该表达式。 Haskell的语法非常简单。非正式地:
>>=
或.
)是中缀(即它们的第一个参数位于标识符的左侧)infix...
声明定义)所以只知道这一点,如果我看到:
mapM unpackNum params >>= return . Number . foldl1 op
首先,我知道它必须像
一样解析(mapM unpackNum params) >>= return . Number . (foldl1 op)
为了更进一步,我们需要检查我们在这个表达式中看到的两个运算符的固定性/优先级:
Prelude> :info (.)
(.) :: (b -> c) -> (a -> b) -> a -> c -- Defined in ‘GHC.Base’
infixr 9 .
Prelude> :info (>>=)
class Applicative m => Monad (m :: * -> *) where
(>>=) :: m a -> (a -> m b) -> m b
...
-- Defined in ‘GHC.Base’
infixl 1 >>=
(.)
具有更高的优先级(9
vs 1
>>=
},因此其参数将更紧密地绑定(即我们首先将它们括起来)。但我们怎么知道哪一个是正确的呢?
(mapM unpackNum params) >>= ((return . Number) . (foldl1 op))
(mapM unpackNum params) >>= (return . (Number . (foldl1 op)))
...?由于(.)
被声明为infixr
,因此它与右侧相关联,这意味着上面的第二个解析是正确的。
正如Will Ness在评论中指出的那样,(.)
是关联的(例如加法),因此这两者恰好在语义上是等价的。
对图书馆(或本案例中的Prelude
)有一点经验,你学会用操作员正确解析表达式而不用太多考虑。
如果您在完成本练习后想要了解函数的功能或工作方式,那么您可以点击查看您感兴趣的函数的来源并替换左侧的函数 - 右侧的手侧(即内联功能和术语的主体)。显然,你可以在头脑或编辑器中做到这一点。
答案 2 :(得分:4)
你可以把它加糖#34;使用更符合初学者的语法,使用符号。您的函数numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op
将成为:
numericBinop op params = do
x <- mapM unpackNum params -- "<-" translates to ">>=", the bind operator
return . Number $ foldl1 op x
现在最神秘的是mapM
函数,即sequence . fmap
,它只需要一个函数, fmaps 就可以在容器上运行,并且翻转类型,在这种情况下(我推测)从[Number Integer]
到ThrowsError [Integer]
,同时保留翻转过程中可能出现的任何错误(副作用),换句话说,如果& #39;翻转&#39;导致任何错误,它将在结果中表示。
这不是最简单的示例,您最好看看mapM (fmap (+1)) [Just 2, Just 3]
与mapM (fmap (+1)) [Just 2, Nothing]
的区别。有关更多见解,请查看Traversable
类型类。
之后,你绑定 [Integer]
monad ThrowsError
并将其提供给负责在列表上进行计算的函数,从而产生一个单Integer
,在将ThrowsError
包裹成return
后,您需要使用Number
函数重新嵌入URL
monad。
如果你仍然无法理解monad,我建议你看一下仍然相关的LYAH chapter,它会轻轻地向你介绍monads
答案 3 :(得分:2)
>>=
构建一个可能在任何一端失败的计算:它的左参数可以是一个空的monad,在这种情况下它甚至不会发生,否则它的结果可能为空好。它有类型
>>= :: m a -> (a -> m b) -> m b
参见,它的参数是:沉浸在monad中的值和一个接受纯值并返回沉浸式结果的函数。例如,此运算符是Scala中所谓的flatMap
的monadic版本;在Haskell中,它对列表的特定实现称为concatMap
。如果您有一个列表l
,则l >>= f
的工作方式如下:对于l
的每个元素,f
将应用于此元素并返回一个列表;并且所有这些结果列表被连接起来以产生结果。
考虑Java中的代码:
try {
function1();
function2();
}
catch(Exception e) {
}
调用function2
时会发生什么?在调用function1
之后,程序可能处于有效状态,因此function2()
是一个将当前状态转换为下一个不同状态的运算符。但是对function1()
的调用可能会导致抛出异常,因此控件会立即转移到catch
- 块 - 这可以被视为空状态,因此无法应用{ {1}}到。换句话说,我们有以下可能的控制路径:
function2
(为简单起见,此图中未考虑从[S0] --- function1() --> [S1] --- function2() --> [S2]
[S0] --- function1() --> [] --> catch
引发的异常。)
因此,function2
是一个(非空)有效机器状态,[S1]
将其进一步转换为(非空)有效function2
,或者它是空的,因此[S2]
是一个无操作,永远不会运行。这可以用伪代码概括为
function2()
答案 4 :(得分:1)
首先,语法。空格是 application ,语义:
f x = f $ x -- "call" f with an argument x
所以你的表达实际上是
numericBinop op params = ((mapM unpackNum) params) >>= return . Number . (foldl1 op)
接下来,运算符由非字母数字字符构成,没有任何空格。在这里,有.
和>>=
。在GHCi上运行:i (.)
和:i (>>=)
会显示其修复程序规范为infixl 9 .
和infixr 1 >>=
。 9
高于1
,因此.
强于>>=
;从而
= ((mapM unpackNum) params) >>= (return . Number . (foldl1 op))
infixl 9 .
表示.
与右侧相关联,因此,最后,它是
= ((mapM unpackNum) params) >>= (return . (Number . (foldl1 op)))
(.)
定义为(f . g) x = f (g x)
,因此(f . (g . h)) x = f ((g . h) x) = f (g (h x)) = (f . g) (h x) = ((f . g) . h) x
;通过eta-reduction我们有
(f . (g . h)) = ((f . g) . h)
因此(.)
是关联的,因此括号是可选的。我们将明确的parens与&#34;空白&#34;从现在开始申请。因此我们有
numericBinop op params = (mapM unpackNum params) >>=
(\ x -> return (Number (foldl1 op x))) -- '\' is for '/\'ambda
使用do
更容易编写单子序列,以上等同于
= do
{ x <- mapM unpackNum params -- { ; } are optional, IF all 'do'
; return (Number (foldl1 op x))) -- lines are indented at the same level
}
接下来,mapM
可以定义为
mapM f [] = return []
mapM f (x:xs) = do { x <- f x ;
xs <- mapM f xs ;
return (x : xs) }
和Monad Laws要求
do { r <- do { x ; === do { x ;
y } ; r <- y ;
foo r foo r
} }
(您可以在我的this recent answer中找到do
符号的概述;因此,
numericBinop op [a, b, ..., z] =
do {
a <- unpackNum a ;
b <- unpackNum b ;
...........
z <- unpackNum z ;
return (Number (foldl1 op [a, b, ..., z]))
}
(您可能已经注意到我对x <- x
绑定的使用 - 我们可以在<-
的两边使用相同的变量名称,因为monadic绑定是不递归 - 从而引入阴影。)
现在希望更清楚了。
但是,我说&#34; 首先,语法&#34;。所以现在,它的意义。同样是Monad Laws,
numericBinop op [a, b, ..., y, z] =
do {
xs <- do { a <- unpackNum a ;
b <- unpackNum b ;
...........
y <- unpackNum y ;
return [a, b, ..., y] } ;
z <- unpackNum z ;
return (Number (op (foldl1 op xs) z))
}
因此,我们只需了解两个&#34;计算&#34;,c
和d
的排序,
do { a <- c ; b <- d ; return (foo a b) }
=
c >>= (\ a ->
d >>= (\ b ->
return (foo a b) ))
对于涉及的特定monad,由给定monad的 bind (>>=
)运算符的实现决定。
Monads是用于广义函数组合的EDSL。计算的顺序不仅涉及出现在do
序列中的显式表达式,还涉及所讨论的特定monad特有的隐含效应,在幕后以原则和一致的方式执行。首先是monad的重点是什么(至少是其中一个要点)。
在这里,涉及的monad似乎关注失败的可能性,以及在失败确实发生的情况下的早期纾困。
因此,使用do
代码,我们编写了我们打算发生的本质,并且在幕后,我们会自动处理间歇性故障的可能性。
换句话说,如果其中一个unpackNum
计算失败,整个合并的计算也会失败,而不会尝试任何后续的{{ 1}}子计算。但如果所有这些都成功,那么合并计算也会成功。