Maybe参数的通用数据构造函数

时间:2014-04-04 15:02:57

标签: haskell

我有一堆数据结构,例如data Foo = Foo {a :: Type1, b :: Type2} deriving (Something)Type1Type2总是不同(通常是原始类型,但它不相关),并且数量不同。

我来了一堆像

这样的功能
justFooify :: Maybe Type1 -> Maybe Type2 -> Maybe Foo
justFooify f b =
  | isNothing f = Nothing
  | isNothing b = Nothing
  | otherwise   = Just $ Foo (fromJust f) (fromJust b)

有什么我想念的吗?在我编写的第三个这样的函数之后,我开始认为可能它可能太多了。

1 个答案:

答案 0 :(得分:9)

你需要申请人!

import Control.Applicative

justFooify :: Maybe Type1 -> Maybe Type2 -> Maybe Foo
justFooify f b = Foo <$> f <*> b

或者您可以在此示例中使用liftA2

justFooify = liftA2 Foo

它的行为与liftM2类似,但适用于Applicative。如果您有更多参数,只需使用更多<*> s:

data Test = Test String Int Double String deriving (Eq, Show)

buildTest :: Maybe String -> Maybe Int -> Maybe Double -> Maybe String -> Maybe Test
buildTest s1 i d s2 = Test <$> s1 <*> i <*> d <*> s2

什么是Applicative?它们基本上是一个更强大的Functor和一个不那么强大的Monad,它们恰好介于两者之间。类型类的定义是

class Functor f => Applicative f where
    pure :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b
    -- plus a few more things that aren't important right now

如果您的Applicative也是Monad,则purereturn相同(事实上,有些人认为return不正确,我们应该只有pure)。但<*>运算符使它们比Functor更强大。它为您提供了一种将函数放入数据结构的方法,然后将该函数应用于也包含在数据结构中的值。所以当你有像

这样的东西时
> :t Test    -- Our construct
Test :: String -> Int -> Double -> String -> Test
> :t fmap Test    -- also (Test <$>), since (<$>) = fmap
fmap Test :: Functor f => f String -> f (Int -> Double -> String -> Test)

我们看到它在Functor内部构造一个函数,因为Test需要多个参数。因此Test <$> Just "a"的类型为Maybe (Int -> Double -> String -> Test)。仅使用Functorfmap,我们无法对Maybe内部应用任何内容,但我们可以使用<*><*>的每个应用程序都将一个参数应用于内部Functor,现在应将其视为Applicative

另一个方便的事情是,它适用于所有 Monads(目前定义他们的Applicative实例)。这意味着列出IO,一个参数的函数,Either e,解析器等。例如,如果您从用户那里获得了构建Test

的输入
askString :: IO String
askInt :: IO Int
askDouble :: IO Double
-- whatever you might put here to prompt for it, or maybe it's read from disk, etc

askForTest :: IO Test
askForTest = Test <$> askString <*> askInt <*> askDouble <*> askString

它仍然有用。这是Applicatives的力量。


仅供参考,在GHC 7.10中将实施Functor-Applicative-Monad Proposal。这将改变Monad的定义

class Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b

class Applicative m => Monad m where
    return :: a -> m a
    return = pure
    (>>=) :: m a -> (a -> m b) -> m b
    join :: m (m a) -> m a

(或多或少)。这将破坏一些旧的代码,但很多人对此感到兴奋,因为它意味着所有Monads都是Applicative并且所有Applicative都是Functors,我们将拥有这些代数对象的全部功能。