函数在参数后写函数

时间:2013-08-04 13:25:34

标签: haskell operators pointfree

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

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

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

我改善那个操作符的下一个要点就是让他调用一次。例如:

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

我尝试使用(>>>) a = a(>>>) a fun = (>>>) (fun a),但看起来这种重载是不可能的。澄清我专注于功能的学习,并且对单子,类型和类别一无所知。

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

4 个答案:

答案 0 :(得分:9)

  

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

     

“仍然不知道关于monad,类型和类的任何信息”

我不认为在不理解其基本概念的情况下尝试用语言“修复”某些东西是个好主意。

首先,Haskell中有流行的库和函数可以提供您想要的内容,例如: lens's & operator完全符合>>>的要求。其次,名称>>>已被Category base 库)实现占用,因此重新实现它并不是一个好主意。基本上它只是一个合成运算符.的反转,但我怀疑你也不熟悉函数组合。第三,绑定(>>=)运算符与你期望的目的相差太远,你需要研究monad来理解它。

答案 1 :(得分:8)

>>=的类型为m a -> (a -> m b) -> m b。在m[](列表)的情况下,它的行为类似于concatMap,因为这些类型排列很好。你所写的是一种完全不同的类型,所以你不能指望它们的工作方式相同。

顺便提一下,你所写的内容已经在Control.Category

中完全按照这种方式定义了
(>>>) :: a b c -> a c d -> a b d

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

第二个代码片段没有多大意义。 $>>>相反,因此您刚刚撰写了

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

或没有那么多的parens

(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模式。总而言之,您无法获得所需的“阅读我的思维”操作符,但. $>>>可以让您自己完成所有操作。

答案 2 :(得分: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. 所以我们可以只用内置的运算符来编写这种风格,

  4.      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。

答案 3 :(得分:2)

monadic绑定操作符>>=总是在monad中返回'wrapped',所以它可能不是你想要的。

您的>>>运算符应该可以通过稍微调整来正常工作,尽管将它与$混合可能没有意义。如果有的话,那只会令人困惑!

您需要的调整是更改运算符的固定性,并使其保持左关联。这意味着在嵌套时不需要括号。

您认为合适的内置$运算符的定义如下:

($) 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