2

我不喜欢在haskell中从右到左阅读函数的顺序。为了解决这个问题,我添加了一个有用的运算符。

module Main where
(>>>)            :: a -> (a -> b) -> b
(>>>) a fun      =  fun a 
main = print $ [1..10] >>> map (*2) >>> filter (>5) >>> foldr1 (+)
-- => 104

我想我可以在这里找到一个类似的内置运算符。像绑定运算符 (>>=)。但是绑定运算符的工作方式不同,或者确切地说我不明白它是如何工作的。它似乎使用 concat 映射。但为什么?

我要改进该操作员的下一点是让他调用一次。例如:

print $ [1..10] >>> map (*2) $ filter (>5) $ foldr (+)

我尝试使用(>>>) a = aand来实现(>>>) a fun = (>>>) (fun a),但似乎这种重载是不可能的。澄清一下,我专注于函数的学习,但对 monads、types 和 classes 仍然一无所知。

所以我的问题是:内置运算符或正确使用绑定运算符。

4

4 回答 4

9

“我不喜欢在 haskell 中从右到左阅读函数的顺序。为了解决这个问题,我添加了一个有用的运算符。”

“仍然对 monad、类型和类一无所知”

我不认为在不了解其基本概念的情况下尝试“修复”该语言中的某些内容是一个好主意。

首先,Haskell 中有一些流行的库和函数可以提供你想要的,例如lens&操作符完全符合你的要求>>>。其次,该名称>>>已经被Category的(基础库的)实现所占用,因此重新实现它不是一个好主意。基本上它只是组合运算符的逆向.,但我怀疑你也不太熟悉函数组合。第三,bind ( >>=) 运算符的用途与您的预期相差太大,您需要研究 monad 才能理解它。

于 2013-08-04T13:55:56.537 回答
8

>>=有类型m a -> (a -> m b) -> m b。在 where mis a [](a list) 的情况下,它的行为确实像concatMap因为类型排列得很好。你写的是一个完全不同的类型,所以你不能指望它们工作相同。

顺便说一句,您所写的内容已经在Control.Category

(>>>) :: a b c -> a c d -> a b d

因此,如果您想拥有良好的旧功能组合,请使用它。它也可以做其他事情,但您不必担心它们。

第二个代码片段没有多大意义。$是相反的>>>所以你刚刚写了

(foldr (+) >>> ( filter (>5) >>> ([1..10] >>> map (*2)))) >>> print

或者没有那么多括号

(foldr (+) >>> filter (>5) >>> [1..10] >>> map (*2)) >>> print

无论您如何超载事物,这都没有意义。相反,使用.

[1..10] >>> print . foldr (+) 0 . filter (>5) . map (*2)

然后你甚至可以>>>完全避免给予

print . foldr (+) 0 . filter (>5) . map (*2) $ [1..10]

这是很常见的 Haskell 模式。总而言之,您无法获得您正在寻找的“读我的想法”运算符,但是. $, 并>>>让您自己走得很远。

于 2013-08-04T13:48:42.463 回答
3
  1. 我认为它|>在 F# 中被调用:

    main = print $ [1..10] |> map (*2) |> filter (>5) |> foldr1 (+)

    请注意,|>它的参数类型是不对称的:它需要一个值和一个函数,并将该值应用于一个函数x |> f = f x:当然,它只是($)翻转:

    x |> f = f x = f $ x = ($) f x = flip ($) x f.

  2. Control.Category中定义了一个类似但不同的运算符(>>>)。有了它,我们可以写

    main = print $ [1..10] |> (map (*2) >>> filter (>5) >>> foldr1 (+))

    请注意,>>>它的参数类型是对称的:它需要两个函数,并产生另一个函数,它们的从左到右组合。当然,它只是(.)翻转:>>> == flip (.),当专门用于函数时:

    f . g $ x = f (g x)

    f >>> g $ x = g (f x) = g . f $ x = flip (.) f g x

  3. 所以我们可以用这种风格编写,只使用内置的运算符,如

     main = print $ ($ [1..10]) (map (*2) >>> filter (>5) >>> foldr1 (+))

哦,你可以用 monadic bind 来做到这一点,你只需要用以下内容来装饰你的代码Control.Monad.Identity

> :m +Control.Monad.Identity
> :m +Control.Arrow
> runIdentity $ Identity 2 >>= return . ((3+) >>> (5*))
25

传统组合顺序的一个案例

一些组合链定义自然从左侧读取的。考虑一下:

res = fix ((2:) . minus [3..] . bigUnion . map (\p-> [p*p, p*p+p..]))

数据流来自右侧,而“理解流”来自左侧。最左边的运算符 ( (2:)) 首先执行,启动计算。用从左到右的合成顺序放在最后可能会感觉不太自然,在这里:

res = (map (\p-> [p*p, p*p+p..]) >>> bigUnion >>> minus [3..] >>> (2:)) |> fix

为了理解最后一个定义,我们不得不从右边读它,尽管它是从左边“写”的。一般来说,

res = fix f     ===   res = f res      -- readable, `f` comes first; obviously
                                       -- `f` produces some part of `res`
                                       -- before actually using it

res = f |> fix  ===   res = res |> f   -- not so much; may create an impression
                                       -- `res` is used prior to being defined

但当然是 YMMV。

于 2013-08-04T14:18:44.563 回答
2

monadic 绑定运算符>>=总是在 monad 中返回“包装”的东西,所以它可能不是你想要的。

您的>>>操作员只需稍加调整即可正常工作,尽管$在您那里进行混合可能没有意义。如果有的话,这只是令人困惑!

您需要进行的调整是更改运算符的固定性,并使其具有左关联性。这意味着嵌套它们时不需要括号。

内置的$操作符,你觉得很舒服,是这样定义的:

($) f a = f a
infixr 0 $

这意味着它具有最低可能的运算符优先级,并且它的关联性在右侧,含义a $ b $ ca $ (b $ c).

由于您的运算符是从右到左的,因此您需要相反的关联性,您可以这样定义:

(>>>) a fun      =  fun a 
infixl 0  >>>

现在,您可以摆脱和的混合使用,$并将>>>您的程序变成管道,而无需添加任何括号:

 [1..10] >>> map (*2) >>> filter (>5) >>> foldr (+) 1 >>> print
于 2013-08-04T14:00:12.040 回答