< $>的简单解释和< *>运营商

时间:2013-11-07 11:40:57

标签: haskell yesod

我必须谈谈Yesod(简单)。是的,..我从来没有或真的很少使用haskell。 大学讲师.....呵呵。

所以我读了一本关于yesod的书,在一些章节中,作者使用了一些运算符,如<$><*>。 有人可以用简单的词语解释,这个运营商做了什么?很难谷歌为那些字符和如果试图阅读Control.Applicative的文档,但说实话,它很难得到一个haskell初学者。

所以我希望有人能给我一个简单的答案:)

使用这些运算符的书的一个例子:

......
personForm :: Html -> MForm Handler (FormResult Person, Widget)
personForm = renderDivs $ Person
    <$> areq textField "Name" Nothing
    <*> areq (jqueryDayField def
        { jdsChangeYear = True -- give a year dropdown
        , jdsYearRange = "1900:-5" -- 1900 till five years ago
        }) "Birthday" Nothing
    <*> aopt textField "Favorite color" Nothing
    <*> areq emailField "Email address" Nothing
    <*> aopt urlField "Website" Nothing
data Person = Person
    { personName          :: Text
    , personBirthday      :: Day
    , personFavoriteColor :: Maybe Text
    , personEmail         :: Text
    , personWebsite       :: Maybe Text
    }
  deriving Show
.....

.....................................

嗨,

非常感谢并且令人惊讶大多数的答案都很有用。可悲的是,只有一个答案能够“解决”。 非常感谢,这个教程(我在谷歌上找不到)非常好

4 个答案:

答案 0 :(得分:9)

在制作主要由链接构成的答案时,我总是非常小心,但this is one amazing tutorial解释了Functors,Applicatives并对Monads进行了一些修改。

答案 1 :(得分:6)

当然,最简单的答案就是类型。这些运算符来自类型类Functor及其子类Applicative

class Functor f where
  fmap :: (a -> b) -> (f a -> f b)

(<$>) = fmap -- synonym

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

最简单直观的答案是FunctorsApplicative s允许您使用“元数据”和(<$>)(<*>)注释简单值,并且朋友可以让您转换“定期“价值级函数,以处理”注释“值。

go x y                    -- works if x and y are regular values
go <$> pure x <*> pure y  -- uses `pure` to add "default" metadata
                          -- but is otherwise identical to the last one

就像任何简单的答案一样,这是一种谎言。 “元数据”是一个非常简单的术语。更好的是“计算环境”或“效果背景”或“容器”。

如果您熟悉Monad,那么您已经非常熟悉这个概念了。所有Monad都是Applicative s,因此您可以将(<$>)(<*>)视为为某些do符号提供替代语法

do x_val <- x                     go <$> x
   y_val <- y                        <*> y
   return (go x_val y_val)

它有更少的符号,并强调将“go”应用于两个参数的想法,而不是强调“获取x生成的值的命令性概念,然后获得{{1}的值生成,然后将这些值应用于y,然后重新包装结果“就像go语法一样。


我可以抛出的最后一个直觉是以一种非常不同的方式来考虑doApplicative相当于另一个名为Applicative的类。

Monoidal

这样class Functor f => Monoidal f where init :: f () -- similar to pure prod :: f a -> f b -> f (a, b) -- similar to (<*>) Monoidal让你(a)以一个微不足道的值实例化它们

Functor

并将其中两个粉碎在一起以生产他们的产品

  init :: [()]
  init = []

  init :: Maybe ()
  init = Just ()

这意味着使用 prod :: [a] -> [b] -> [(a, b)] prod as bs = [(a, b) | a <- as, b <- bs] prod :: Maybe a -> Maybe b -> Maybe (a, b) prod (Just a) (Just b) = (Just (a, b)) prod _ _ = Nothing 仿函数可以将大量值一起粉碎,然后Monoidal在整个群体中使用值级函数

fmap

这基本上就是您使用go <$> maybeInt `prod` (maybeChar `prod` maybeBool) where go :: (Int, (Char, Bool)) -> Double -- it's pure! go (i, (c, b)) = ... (<$>)所做的事情,只需更少的元组

(<*>)

最后,这是你如何在两个概念之间进行转换

go <$> maybeInt <*> maybeChar <*> maybeBool where
  go :: Int -> Char -> Bool -> Double
  go i c b = ...

显示了如何将-- forward init = pure () prod x y = (,) <$> x <*> y -- back pure a = const a <$> init f <*> x = ($) <$> prod f x 视为采用正常的价值级应用(<*>)并将其注入($)内的prod内部。< / p>

答案 2 :(得分:5)

我认为<$>只是fmap的中缀同义词并不是一件容易的事。但是,这些例子可能有助于澄清:

GHCi> (*2) <$> (Just 3)
Just 6
GHCi> (*2) <$> (Nothing)
Nothing
GHCi> (*3) <$> (Right 7)
Right 21
GHCi> (*2) <$> (Left "error")
Left "error"
GHCi>  (+ 1) <$> [2,4,6,8]
[3,5,7,9]

现在将其与此进行比较:

GHCi> (*) <$> (Just 2) <*> (Just 5)
Just 10
GHCi> (*) <$> (Just 2) <*> (Nothing)
Nothing
GHCi> (*) <$> (Right 3) <*> (Right 7)
Right 21
GHCi> (*) <$> (Left "error") <*> (Right 7)
Left "error"
GHCi> (+) <$> [1,2,3] <*> [10,20,30]
[11,21,31,12,22,32,13,23,33]
GHCi> (+) <$> [1,2,3] <*> []
[]

然后到此:

GHCi> (Just (*2)) <*> (Just 5)
Just 10
GHCi> (Right (*3)) <*> (Right 7)
Right 21
GHCi> [(+1),(+2),(+3)] <*> [10,20,30]
[11,21,31,12,22,32,13,23,33]

真的,如果您从中了解到(*) <$> (Just 2) <*> (Just 5)等同于Just (2 * 5)

,那么这应该向您展示您需要了解的所有内容。

(顺便说一句,在第一组例子中,左侧的功能都是应用程序。)

简单地说,<$>将左边的函数和提升放到右边的“框中的东西”的上下文中,以便它可以应用于事物在框中,以符合框的特殊规则的方式(例如Nothng导致整个链失败)。

<*>在左侧的框中使用部分绑定函数,并将其应用于右侧框中的值。部分绑定的函数是一个已经给出一些但不是所有参数的函数。因此,(*) <$> (Right 3) <*> (Right 7) <*> (Right 4)会失败 - 会显示一条不太有用的错误消息 - 因为*已应用于37后,它不再是部分功能而且没有人知道如何处理4

一起使用,<$><*>允许将函数应用于其参数,所有这些都在一个框内。你可以在一个方框中得到结果。

如果盒子本身就是一个仿函数,那么这一切都可以完成;这是所有这一切的关键约束。仿函数是一个函数,有人已经定义了一个fmap函数,它允许它从一个应用于一种类型的函数转换为一个适用于另一种类型的函数(同时不改变函数的基本特征) 。如果您愿意,Monads(物联网)知道如何转换函数,以便它们可以应用于它们的东西。

答案 3 :(得分:3)

如果您尚未准备好了解仿函数,应用程序和monad,这可能会让您对如何使用<$><*>有所了解。 (在我真正了解其他内容之前,我自己学会了如何使用它们。)在没有<$><*>的情况下,该代码的第一部分看起来像这样:

......
personForm :: Html -> MForm Handler (FormResult Person, Widget)
personForm = do
    name <- areq textField "Name" Nothing
    bday <- areq (jqueryDayField def
        { jdsChangeYear = True -- give a year dropdown
        , jdsYearRange = "1900:-5" -- 1900 till five years ago
        }) "Birthday" Nothing
    colour <- aopt textField "Favorite color" Nothing
    email <- areq emailField "Email address" Nothing
    url <- aopt urlField "Website" Nothing
    renderDivs $ Person name bday colour email url

换句话说,<$><*>可以消除创建我们只使用过一次的许多符号的需要。