自动将中缀运算符提升为单子中缀运算符

9
使用 Haskell 的一个好处是能够使用中缀表示法。
1 : 2 : 3 : []    :: Num a => [a]
2 + 4 * 3 + 5     :: Num a => a

但是当操作员需要抬起时,这种力量突然而令人难过地丧失了。

liftM2 (*) (liftM2 (+) m2 m4) (liftM2 (+) m3 m5)
liftM2 (:) m1 (liftM2 (:) m2 (liftM2 (:) m3 mE))

可以定义类似的运算符来恢复这种能力。

(.*) = liftM2 (*)
(.+) = liftM2 (+)
(.:) = liftM2 (:)

m1, m2, m3, m4, m5 :: Monad m, Num a => m a
mE = return []     :: Monad m => m [a]
m1 .: m2 .: m3 .: mE    :: Monad m, Num a => m [a]
m2 .+ m4 .* m3 .+ m5    :: Monad m, Num a => m a

但是在单子上下文中需要重命名我想使用的每个运算符很麻烦。有更好的方法吗?也许是Template Haskell?


如果mX是IO或ST操作,您可能不希望多次执行它们,因为liftM2 (+) m m与do n <- m; return n + n非常不同。 - Ingo
SHE有成语括号,让你可以写(|f m1 m2 ... mn|)代替(pure f <*> m1 <*> m2 <*> ... <*> mn),以及(|m1 <> m2|)代替(pure (<>) <*> m1 <*> m2)。我还没有实现一个解决由中缀运算符构建的更复杂表达式的优先级提升器。 - pigworker
@Ingo,语义意思确实是要多次运行效果。例如,liftM2 (+) readInt readInt - 在这种情况下,我希望执行两次readInt操作,而不仅仅是一次,因为第二次可能会获取不同的整数。 - Dan Burton
3个回答

10

你可以使所有的单子实例成为 Num 的一个实例:

{-# LANGUAGE FlexibleInstances, FlexibleContexts, UndecidableInstances #-}

import Control.Monad

instance (Monad m, Num n, Show (m n), Eq (m n)) => Num (m n) where
  (+) = liftM2 (+)
  (*) = liftM2 (*)

接下来你可以这样做:

*Main> [3,4] * [5,6] + [1,2]
[16,17,19,20,21,22,25,26]

但是这仅适用于使用类型类定义的运算符。 对于:,这是不可能的。


2
你可能需要为大多数有趣的单子添加虚拟的 ShowEq 实例来使用它。 - Daniel Wagner

9
您可以定义一个新的中缀操作符:
v <. f = liftM2 f v
f .> v = f v

使用示例:

[3] <.(+).> [4]

但我不知道是否有任何真正的方法,而这些方法又不会让人感到100%的烦恼。


是的...我想如果反复使用相同的单子运算符,简单地制作自己的自定义运算符会更少繁琐。 - Dan Burton
@Dan,如果您一遍又一遍地使用相同的单子操作符——也就是说,您发现自己将相同的纯操作符提升到单子中多次,我敢猜测您的代码可能需要更少的单子——那样会更短、更简单。 - luqui

2

有一种使用ap的样式:

return (:) `ap` Just 1 `ap` Just []

或应用风格:
(:) <$> Just 1 <*> Just []

1
这是可行的,但需要运算符出现在前缀位置,而我想避免这种情况。 - Dan Burton
1
当然,您可以定义 x <%> y = y <$> x。这使您能够编写 Just 1 <%> (:) <*> Just []。但是我不知道如何在没有这些烦人的括号的情况下完成。 - Zopa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接