这是将类型t
上的运算符转换为类型a->t
上的运算符的标准方法吗?即,这是一个实现此功能的lib(fT
,如functionTransformer
):
fT :: (t -> t -> t) -> (a -> t) -> (a -> t) -> (a -> t)
fT op f1 f2 = \x -> (f1 x) `op` (f2 x)
(我们可以概括为fT :: (t1 -> t2 -> t) -> (t3 -> t1) -> (t3 -> t2) -> t3 -> t
)
我在学习Yesod时问这个问题:在这个框架中,我们可以通过checkBool
为字段添加验证条件。例如,我可以创建一个只接受100以上的值的字段:
smallIntField = checkBool (<= 100) "error: this entry has to be smaller than 100" intField
感谢我的&#34;函数转换器&#34;,我可以轻松管理有界值:
($&&) = fT (&&)
boundedIntField = checkBool ((>= 0) $&& (<= 100)) "error: this entry has to be between 0 and 100" intField
答案 0 :(得分:6)
您真正需要的是liftA2
或liftM2
组合子(分别来自Control.Applicative
或Control.Monad
)。 liftM2
与liftA2
的工作方式相同,因为所有monad都是应用程序,但如果你想要或多或少的限制,这取决于你。使用monadic实现也会强制对参数进行评估,liftA2
无法保证。
您可以将其用作
(<&&>) :: Applicative f => f Bool -> f Bool -> f Bool
(<&&>) = liftA2 (&&)
对于(>= 0)
或(<= 100)
等功能,Applicative f => f
专门针对(Ord a, Num a) => (->) a
,因此类型签名将为
(<&&>) :: (Ord a, Num a) => (a -> Bool) -> (a -> Bool) -> (a -> Bool)
所以你可以写点像
between :: Int -> Int -> Int -> Bool
between lower upper = (lower <=) <&&> (<= upper)
如果您定义了<||> = liftM2 (||)
,那么您甚至可以执行更复杂的操作,例如
between 0 100 <||> between 200 400 <||> (>= 1000) :: Int -> Bool
如果我们用set notation([0, 100] U [200, 400] U [1000, inf)
设置了union)写这个数字,那么检查该数字是否是U
的元素。
答案 1 :(得分:2)
此代码位于Control.Applicative
模块中:
instance Applicative ((->) r) where
pure a = \_ -> a
ff <*> fa = \r -> ff r (fa r)
liftA2 :: Applicative f => f (a -> b -> c) -> f a -> f b -> f c
liftA2 f fa fb = pure f <*> fa <*> fb
我们fT
功能正是我们采用liftA2
并使用(->) a
代表f
,t
代表a
时所获得的功能, b
和c
。我们可以像这样重写你的函数:
fT :: (t -> t -> t) -> (a -> t) -> (a -> t) -> (a -> t)
fT = liftA2
通过替换,我们可以证明它与您的相同:
liftA2 op f1 f2
= (pure op <*> f1) <*> f2
= \a -> (pure op <*> f1) a (f2 a)
= \a -> (\a -> pure op a (fa 1)) a (f2 a)
= \a -> (\a -> (\_ -> op) a (f1 a)) a (f2 a)
= \a -> (\a -> op (f1 a)) a (f2 a)
= \a -> op (f1 a) (f2 a)
= \a -> (f1 a) `op` (f2 a)