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

dmz*_*rsk 6 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
Run Code Online (Sandbox Code Playgroud)

现在我想validate :: Integer -> Maybe Integereven和建造isSmall

我最好的解决方案是

validate a = isSmall a *> even a *> Just a
Run Code Online (Sandbox Code Playgroud)

这不是免费的

我可以使用monad

validate x = do
  even x
  isSmall x
  return x
Run Code Online (Sandbox Code Playgroud)

但是为什么要使用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)
Run Code Online (Sandbox Code Playgroud)

我需要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
Run Code Online (Sandbox Code Playgroud)

但我想的是,元组输入"流"greater,然后"流"到某种的组合物sndeven然后仅单个元件在最终结束Just.

哈斯克勒会做什么?

Cir*_*dec 7

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

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

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

infixr 3 >*>
Run Code Online (Sandbox Code Playgroud)

和写

validate = isSmall >*> even
Run Code Online (Sandbox Code Playgroud)

你的第二个例子是

validate = even . snd >*> greater
validate' = even . snd >*> fmap snd . greater
Run Code Online (Sandbox Code Playgroud)

它们以不同的顺序检查条件.如果您关心评估顺序,则可以定义另一个功能<*<.

ReaderT

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

newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
Run Code Online (Sandbox Code Playgroud)

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

instance (Applicative m) => Applicative (ReaderT r m) where
    f <*> v = ReaderT $ \ r -> runReaderT f r <*> runReaderT v r
    ...
Run Code Online (Sandbox Code Playgroud)

ReaderT<*>几乎完全一样>*>从第一部分,但它并没有丢弃的第一个结果.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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

您可以使用这些中的一个ReaderT验证器上的值xrunReaderT validate x


Jus*_* L. 5

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

你所做的一切主要是试图利用monoid行为/ Monoid,但试图通过Applicative接口来实现.有点像有工作Int,通过自己的字符串表示的(实现+了串"1""12",并串,而不只是工作112与工作整数)

请注意,您可以Applicative从任何Monoid实例获取实例,因此找到可以解决问题的Monoid与找到可以实现的Applicative相同.

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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

你可以直接写你的validate:

validate :: Int -> All
validate a = isSmall a <> even a
Run Code Online (Sandbox Code Playgroud)

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

validate :: Int -> All
validate = isSmall <> even
Run Code Online (Sandbox Code Playgroud)

想做记号吗?

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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,每个Monoid实例都会产生Applicative/ Monadinstance(通过Writertell),这使得这有点方便.您可以将Writer/ tell在这里视为"提升" Monoid实例到free Applicative/ Monadinstance.

最后,你注意到一个有用的设计模式/抽象,但它确实是你注意到的幺半群.你已经注意通过Applicative接口处理那个monoid,不知何故......但是直接使用monoid可能更简单.

也,

validate :: Int -> All
validate = mconcat
  [ isSmall
  , even
  , other validator
  ]
Run Code Online (Sandbox Code Playgroud)

可以说与写作版本的符号版本清晰可比:)