在Haskell中结合使用应用程序风格的验证器

时间:2016-12-20 08:24:54

标签: haskell composition applicative

我对命令式编程有很好的把握,但是现在我学会了很好的Haskell。

我认为,我对Monads,Functors和Applicatives有很好的理论认识,但我需要一些练习。而对于练习,我有时会从当前的工作任务中带来一些内容。

我在应用方式中加入了一些东西

第一个问题

我有两个验证功能:

import Prelude hiding (even)

even :: Integer -> Maybe Integer
even x = if rem x 2 == 0 then Just x else Nothing

isSmall :: Integer -> Maybe Integer
isSmall x = if x < 10 then Just x else Nothing

现在我希望validate :: Integer -> Maybe Integereven

构建isSmall

我最好的解决方案是

validate a = isSmall a *> even a *> Just a

它不是免费的

我可以使用monad

validate x = do
  even x
  isSmall x
  return x

但是为什么要使用Monad,如果(我想)我需要的只是一个适用者? (它仍然没有点免费)

这样做是否更好(也更加坦白)?

第二个问题

现在我有两个具有不同签名的验证器:

even = ...

greater :: (Integer, Integer) -> Maybe (Integer, Integer)
-- tuple's second element should be greater than the first
greater (a, b) = if a >= b then Nothing else Just (a, b)

我需要validate :: (Integer, Integer) -> Maybe (Integer, Integer),它会在输入元组上尝试greater,然后在元组的第二个元素上尝试even

validate' :: (Integer, Integer) -> Maybe Integer具有相同的逻辑,但返回元组的第二个元素。

validate  (a, b) = greater (a, b) *> even b *> Just (a, b)
validate' (a, b) = greater (a, b) *> even b *> Just  b

但是我想输入元组&#34;流动&#34; greater,然后&#34;流动&#34; 到一些sndeven的一种组合,然后只有一个元素在最终的Just中结束。

哈斯克勒会做什么?

2 个答案:

答案 0 :(得分:7)

当您编写a -> Maybe b形式的验证器时,您对整个类型比对Maybe应用程序更感兴趣。类型a -> Maybe bMaybe monad的Kleisli箭头。您可以制作一些工具来帮助使用此类型。

对于第一个问题,您可以定义

(>*>) :: Applicative f => (t -> f a) -> (t -> f b) -> t -> f b
(f >*> g) x = f x *> g x

infixr 3 >*>

并写

validate = isSmall >*> even

你的第二个例子是

validate = even . snd >*> greater
validate' = even . snd >*> fmap snd . greater

这些以不同的顺序检查条件。如果您关心评估顺序,则可以定义另一个函数<*<

ReaderT

如果您经常使用类型a -> Maybe b,则可能需要为其创建newtype,以便您可以根据自己的需要添加自己的实例。 newtype已经存在;它是ReaderT,它的实例已经做了你想做的事。

newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }

当您使用类型r -> Maybe a作为验证器来验证和转换单个输入r时,它与ReaderT r Maybe相同。 Applicative instance for ReaderT通过将两个函数应用于同一输入,然后将它们与<*>组合在一起,将其中的两个组合在一起:

instance (Applicative m) => Applicative (ReaderT r m) where
    f <*> v = ReaderT $ \ r -> runReaderT f r <*> runReaderT v r
    ...

ReaderT&#39; <*>与第一部分的>*>几乎完全相同,但它不会丢弃第一个结果。 ReaderT *>与第一部分中的>*>完全相同。

ReaderT而言,您的示例将成为

import Control.Monad.Trans.ReaderT

checkEven :: ReaderT Integer Maybe Integer
checkEven = ReaderT $ \x -> if rem x 2 == 0 then Just x else Nothing

checkSmall = ReaderT Integer Maybe Integer
checkSmall = ReaderT $ \x -> if x < 10 then Just x else Nothing

validate = checkSmall *> checkEven

checkGreater = ReaderT (Integer, Integer) Maybe (Integer, Integer)
checkGreater = ReaderT $ \(a, b) = if a >= b then Nothing else Just (a, b)

validate = checkGreater <* withReaderT snd checkEven
validate' = snd <$> validate

您可以ReaderT使用其中一个x验证码runReaderT validate x

答案 1 :(得分:5)

如果你需要的是申请人,你问为什么要使用Monad?我可以问 - 为什么使用Applicative,如果您只需要Monoid?

你所做的一切主要是试图利用monoid行为/ Monoid,但试图通过Applicative接口来实现。有点像通过字符串表示使用Int&#{为字符串+"1"实施"12"并使用字符串而不仅仅是{{1} }和1并使用ints)

请注意,您可以从任何12实例获取Applicative实例,因此找到可以解决问题的Monoid与找到可以解决问题的应用程序相同。

Monoid

为了证明它们是相同的,我们可以来回编写转换函数:

even :: Integer -> All
even x = All (rem x 2 == 0)

isSmall :: Integer -> All
isSmall x = All (x < 10)

greater :: (Integer, Integer) -> All
greater (a, b) = All (b > a)

您可以直接撰写convertToMaybeFunc :: (a -> All) -> (a -> Maybe a) convertToMaybeFunc f x = guard (getAll (f x)) $> x -- assuming the resulting Just contains no new information convertFromMaybeFunc :: (a -> Maybe b) -> (a -> All) convertFromMaybeFunc f x = maybe (All False) (\_ -> All True) (f x)

validate

但你也可以用你想要的提升风格来写它:

validate :: Int -> All
validate a = isSmall a <> even a

想要记号吗?

validate :: Int -> All
validate = isSmall <> even

正如您所看到的,每个validate :: Int -> All validate = execWriter $ do tell isSmall tell even tell (other validator) validate' :: (Int, Int) -> All validate' = execWriter $ do tell (isSmall . fst) tell (isSmall . snd) tell greater 个实例都会产生Monoid / Applicative个实例(通过MonadWriter),这会让您感到有点方便。您可以将tell / Writer视为&#34;提升&#34; tell实例到免费的Monoid / Applicative个实例。

最后,您注意到一个有用的设计模式/抽象,但它确实是您注意到的 monoid 。你通过Applicative接口专注于处理那个monoid,不知怎的......但是直接使用monoid可能更简单。

此外,

Monad

可以说明显与编纂版本符号相似:)