我不想在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运算符。
答案 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)
我认为它在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
。
在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
所以我们可以只用内置的运算符来编写这种风格,
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 $ c
与a $ (b $ c)
相同。
由于您的操作员是从右到左,您将需要相反的关联性,您可以这样定义:
(>>>) a fun = fun a
infixl 0 >>>
现在,您可以摆脱$
和>>>
的混合使用,并将您的程序变为管道,而不添加任何括号:
[1..10] >>> map (*2) >>> filter (>5) >>> foldr (+) 1 >>> print