4

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

所以我读了一本关于 yesod 的书,在某些章节中作者使用了一些运算符,比如<$>and <*>。有人可以用简单的话解释一下,这个运营商是做什么的?用谷歌搜索这些字符非常困难,如果尝试阅读 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

4 回答 4

9

在做出主要由链接组成的答案时,我总是非常小心,但这是一个了不起的教程,它解释了 Functors、Applicatives 并给出了对 Monads 的一些理解。

于 2013-11-07T11:54:41.040 回答
6

最简单的答案当然是类型。这些运算符来自类型 typeclassesFunctor及其子类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

最简单直观的答案是FunctorsApplicatives 让您可以使用“元数据”和(<$>),来注释简单值(<*>),而朋友可以让您将“常规”值级函数转换为处理“注释”值。

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

就像任何简单的答案一样,这有点像谎言。“元数据”是一个非常简单的术语。更好的是“计算上下文”或“效果上下文”或“容器”。

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

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

它的符号更少,并强调“应用”go到两个参数的想法,而不是强调“获取x生成的值,然后获取y生成的值,然后将这些值应用于go,然后重新包装结果”的命令式概念,例如do语法。


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

class Functor f => Monoidal f where
  init :: f ()                             -- similar to pure
  prod :: f a -> f b -> f (a, b)           -- similar to (<*>)

这样你就Monoidal Functor可以(a)用一个微不足道的值来实例化它们

  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)) = ...

这本质上就是你正在用(<$>)and做的(<*>),只是用更少的元组

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

这显示了您如何可以(<*>)将正常的值级应用程序视为将($)其注入prodFunctor.

于 2013-11-07T13:31:49.417 回答
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)会失败 - 带有一个不是很有帮助的错误消息 - 因为一旦*被应用于3并且7它不再是一个部分函数并且没有人知道如何处理4.

一起使用,<$><*>允许将函数应用于其参数,所有这些都在一个盒子内。你会在一个盒子里得到结果。

这一切只有在盒子本身是函子的情况下才能完成;这是所有这一切的关键制约因素。仿函数是一个函数,有人为其定义了一个fmap函数,该函数允许它从适用于一种类型的函数转换为适用于另一种类型的函数(同时不改变函数的基本特征)。如果您愿意,Monads(事物的盒子)知道如何转换函数,以便将它们应用于他们的事物。

于 2013-11-07T13:38:20.210 回答
3

如果您还没有准备好学习函子、应用程序和 monad,这可能会让您对如何使用<$><*>. (在我真正理解其他内容之前,我自己通过查看示例学习了如何使用它们。)如果没有<$>and <*>,该代码的第一部分将如下所示:

......
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

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

于 2013-11-07T12:02:54.180 回答