将monadic验证转变为适用性?
我需要验证数字输入是否在某个范围内。为此,我正在使用
ensure :: (a -> Bool) -> a -> Maybe a
ensure p v | p v = Just v
| otherwise = Nothing
检查某个值的上限和下限的一种方法x :: Int是通过一元链:
let validated = pure x >>= ensure (>0) >>= ensure (<100)
据我了解,两次验证的顺序无关紧要;因此,应该可以以应用形式重写上述表达式。如何?我没能做到,但我希望一旦我做到了,我就能对应用程序有更深入的了解:-)。
回答
这里要注意的非常微妙的事情是“直观地”返回值ensure并不重要。仅重要的是它是Just还是Nothing。为了表达这一点,你可以给它一个更诚实的类型签名:
ensure :: (a -> Bool) -> a -> Maybe ()
ensure p v | p v = Just ()
| otherwise = Nothing
现在,由于返回值无关紧要,您应该可以安全地忽略它。所以逻辑变成了这样:调用ensure (>0)and ensure (<100),然后将它们组合在一起,忽略它们的返回值,但保留它们的Maybe形状。
为此,您确实可以使用 applicative。您将要应用的函数将执行我上面所说的:忽略两个返回值。“保留Maybe形状”部分将由Applicative实例透明地处理。所以:
let validated = (_ _ -> x) <$> ensure (>0) x <*> ensure (<100) x
看看我的应用函数如何忽略两个参数并返回x自身?
但是,当然,ensure正如您的问题中所写的那样, 的类型签名表明它可能不仅仅做验证。当然,当它完全通用时它真的不能,但如果它更具体一点就可以:
ensure :: (Int -> Bool) -> Int -> Maybe Int
ensure p v | p v = Just (v + 100)
| otherwise = Nothing
现在订单突然很重要:
pure (-5) >>= ensure (>0) >>= ensure (<100) == Nothing
pure (-5) >>= ensure (<100) >>= ensure (>0) == Just 95
所以这里的底线是:目的ensure有点不明确。真的回来了Maybe a吗?那么顺序可能很重要。是真正的返回类型Maybe ()吗?然后你可以使用Applicative和忽略这些单位。
回答
您可以使用liftA2组合两个谓词:
> import Control.Applicative
> :t liftA2 (&&) (> 0) (< 100)
liftA2 (&&) (> 0) (< 100) :: (Ord a, Num a) => a -> Bool
此函数具有与 一起使用的正确类型ensure:
validate :: Num a => a -> Bool
validate f g = ensure (liftA2 (&&) f g)
然后
> validate (> 0) (< 100) 50
Just 50
> validate (> 0) (< 100) 1000
Nothing
- I -1ed because of the lack of explaination of the `(a->)` applicative. While this is certainly a sensible use case for it, it can be very confusing to beginners, especially if the question was asked in the context of the completely different Maybe monad.
- For a list of predicates: `validate ps = ensure (all . sequenceA ps)`