将 monadic 验证变成应用程序?
Turn monadic validation into applicative?
我需要验证数字输入是否在特定范围内。为此,我正在使用
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 :: (a -> Bool) -> a -> Maybe a
ensure p v | p v = Just v
| otherwise = Nothing
实际上是
ensure :: Alternative m => (a -> Bool) -> a -> m a
ensure p v = if p v then pure v else empty
所以
validated x = pure x >>= ensure (>0) >>= ensure (<100)
= do
a <- pure x
b <- ensure (>0) a
c <- ensure (<100) b
pure c
= do
b <- ensure (>0) x
c <- ensure (<100) b
pure c
如果 ensure
是一个不透明函数,我们将不得不到此为止。
但是因为我们知道它的来源,所以我们可以继续,
= do
b <- if (>0) x then pure x else empty
c <- if (<100) b then pure b else empty
pure c
= do
_ <- if (>0) x then pure () else empty
_ <- if (<100) x then pure () else empty
pure x
= do
if (>0) x then pure () else empty
if (<100) x then pure () else empty
pure x
= guard ((>0) x) >> guard ((<100) x) >> pure x
= [ x | x > 0, x < 100 ] -- with MonadComprehensions
这个计算本质上没有什么一元的,没有在上一步计算的值在这里计算下一个计算步骤,只是从给定值计算的一个计算步骤,x
:
= guard ((>0) x) *> guard ((<100) x) *> pure x
= liftA2 (\_ _-> x) (guard $ (>0) x) (guard $ (<100) x)
= when (x>0) (when (x<100) $ pure ()) *> pure x
= when (and [x>0, x<100]) (pure ()) *> pure x
最后一个表达式的推断类型是Applicative f => f b, x :: b
。
这里要注意的非常微妙的一点是,“直觉上”ensure
的 return 值并不重要。重要的只是它是 Just
还是 Nothing
。为了表达这一点,您可以给它一个更诚实的类型签名:
ensure :: (a -> Bool) -> a -> Maybe ()
ensure p v | p v = Just ()
| otherwise = Nothing
现在,由于 return 值无关紧要,您应该安全地忽略它。所以逻辑变成了这样:调用 ensure (>0)
和 ensure (<100)
,然后将它们组合在一起,忽略它们的 return 值,但保留它们的 Maybe
形状。
为此,您确实可以使用 applicative。您要应用的函数将执行我上面所说的:忽略两个 return 值。 “保留 Maybe
形状”部分将由 Applicative
实例透明处理。所以:
let validated = (\_ _ -> x) <$> ensure (>0) x <*> ensure (<100) x
看看我应用的函数如何忽略参数和 returns 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
的目的有点模棱两可。真的是returnMaybe a
吗?那么顺序可能很重要。真正的return类型是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
没有。 Applicative 和 Monad 之间的区别虽然都在应用程序之间强制执行顺序,但 Monad 可以接受链中其他操作的值,而 Applicative 不能。 Maybe 有一个 Applicative 实例,但它的工作方式就像 Maybe Monoid:returns 最左边的 Just
值。换句话说,Applicative 实例适用于 Maybe a
的值,但不适用于 a -> Maybe a
.
我需要验证数字输入是否在特定范围内。为此,我正在使用
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 :: (a -> Bool) -> a -> Maybe a
ensure p v | p v = Just v
| otherwise = Nothing
实际上是
ensure :: Alternative m => (a -> Bool) -> a -> m a
ensure p v = if p v then pure v else empty
所以
validated x = pure x >>= ensure (>0) >>= ensure (<100)
= do
a <- pure x
b <- ensure (>0) a
c <- ensure (<100) b
pure c
= do
b <- ensure (>0) x
c <- ensure (<100) b
pure c
如果 ensure
是一个不透明函数,我们将不得不到此为止。
但是因为我们知道它的来源,所以我们可以继续,
= do
b <- if (>0) x then pure x else empty
c <- if (<100) b then pure b else empty
pure c
= do
_ <- if (>0) x then pure () else empty
_ <- if (<100) x then pure () else empty
pure x
= do
if (>0) x then pure () else empty
if (<100) x then pure () else empty
pure x
= guard ((>0) x) >> guard ((<100) x) >> pure x
= [ x | x > 0, x < 100 ] -- with MonadComprehensions
这个计算本质上没有什么一元的,没有在上一步计算的值在这里计算下一个计算步骤,只是从给定值计算的一个计算步骤,x
:
= guard ((>0) x) *> guard ((<100) x) *> pure x
= liftA2 (\_ _-> x) (guard $ (>0) x) (guard $ (<100) x)
= when (x>0) (when (x<100) $ pure ()) *> pure x
= when (and [x>0, x<100]) (pure ()) *> pure x
最后一个表达式的推断类型是Applicative f => f b, x :: b
。
这里要注意的非常微妙的一点是,“直觉上”ensure
的 return 值并不重要。重要的只是它是 Just
还是 Nothing
。为了表达这一点,您可以给它一个更诚实的类型签名:
ensure :: (a -> Bool) -> a -> Maybe ()
ensure p v | p v = Just ()
| otherwise = Nothing
现在,由于 return 值无关紧要,您应该安全地忽略它。所以逻辑变成了这样:调用 ensure (>0)
和 ensure (<100)
,然后将它们组合在一起,忽略它们的 return 值,但保留它们的 Maybe
形状。
为此,您确实可以使用 applicative。您要应用的函数将执行我上面所说的:忽略两个 return 值。 “保留 Maybe
形状”部分将由 Applicative
实例透明处理。所以:
let validated = (\_ _ -> x) <$> ensure (>0) x <*> ensure (<100) x
看看我应用的函数如何忽略参数和 returns 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
的目的有点模棱两可。真的是returnMaybe a
吗?那么顺序可能很重要。真正的return类型是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
没有。 Applicative 和 Monad 之间的区别虽然都在应用程序之间强制执行顺序,但 Monad 可以接受链中其他操作的值,而 Applicative 不能。 Maybe 有一个 Applicative 实例,但它的工作方式就像 Maybe Monoid:returns 最左边的 Just
值。换句话说,Applicative 实例适用于 Maybe a
的值,但不适用于 a -> Maybe a
.