5

我一直在玩 Yesod,然后出现了一个问题:表单如何用作应用程序?

拿:

    personForm :: Html -> MForm Synopsis Synopsis (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

我不明白如何将<*>运算符(据我理解)将某种类型f (a -> b)(即包含二进制函数的函子)作为其第一个参数应用于AForm值:

(AForm f) <*> (AForm g) = AForm $ \mr env ints -> do ...

这里发生了什么?

https://github.com/yesodweb/yesod/blob/bf293e6a1f6e691281520d254f72b9441cc64704/yesod-form/Yesod/Form/Types.hs#L95

4

1 回答 1

13

如果您尝试稍微忽略详细类型并查看全局,这会有所帮助。

<$>其视为在不同级别上工作的<*>特殊版本。$

一般的应用函子

让我们先看一下一般情况 - 假设我有一个应用函子AF和对象

x :: AF a
y :: AF b
z :: AF c

有点意思他们“做某事”然后返回类型的值abc,还有一个纯函数

f :: a -> b -> c -> d

我想用来将这些值组合在一起以获得d. 然后

f <$> x <*> y <*> z    :: AF d

通过“做” x,然后yz然后应用于f结果来工作。

请注意,这类似于f $ a $ b $ c.

如果您的应用函子与 monad 实例一致,这f <$> x <*> y <*> z是一种很好的写作方式

do
  a <- x
  b <- y
  c <- z
  return (f a b c)

作为应用函子的形式

将表单视为产生数据(来自用户)的东西。areq并且aopt都返回一个AForm sub master ??. 您可以忽略suband master- 它们用于使用类型系统跟踪站点/子站点。??是返回的数据类型。

所以
areq textField "Name" Nothing是一个产生的小表格Text
areq (jqueryDayField def) "Birthday" Nothing一个产生a的小表格,Day另外
三个也产生Text

现在我们有

data Person = Person Text Day Text Text Text

所以这Person是一个函数:: Text -> Day -> Text -> Text -> Text -> Person,所以如果你有

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

Person通过获取字段的所有单独值并将纯Person函数应用于它们,您已将所有这些表单组合成一个生成单个值的表单。

要以单子风格写,你会写

do
    name    <- areq textField "Name" Nothing
    day     <- areq (jqueryDayField def
              { jdsChangeYear = True -- give a year dropdown
              , jdsYearRange = "1900:-5" -- 1900 till five years ago
              }) "Birthday" Nothing
    color   <- aopt textField "Favorite color" Nothing
    email   <- areq emailField "Email address" Nothing
    website <- aopt urlField "Website" Nothing

    return $ Person name day color email website

我非常喜欢 applicative functor 风格,因为它感觉就像将函数应用于某些数据,而不是执行一系列指令。

那么<$>vs有什么关系<*>呢?

你可能已经注意到我总是这样做

pureFunction <$> af1 <*> af2 <*> af3 <*> af4 ....

首先<$><*>其余的。那是因为第一件事pureFunction是纯粹的,并且不适用于应用函子值。它举起它。(所有应用函子都是函子。)让我们比较一下类型:

<$> :: Functor f     =>    (a -> b) -> f a -> f b
<*> :: Applicative f =>  f (a -> b) -> f a -> f b

这意味着这<$>是为了提升一个纯函数,但是当您使用<*>左侧时,必须已经在生成函数而不仅仅是数据,这最初看起来很奇怪,但如果您<$>先使用则不会。例如,如果你部分应用(++)"Hello"你得到一个函数:: String -> String,那么

         getLine             :: IO String             -- produces a String
(++) <$> getLine             :: IO (String -> String) -- produces an appender
(++) <$> getLine <*> getLine ::            IO String  -- produces a combined String

将其与

areq textField "Name" Nothing
   :: AForm sub master Text

因为Person :: Text -> Day -> Text -> Text -> Text -> Person,如果我们给它一个值,我们得到一个类型name::Text的部分应用函数Person nameDay -> Text -> Text -> Text -> Person

Person <$> areq textField "Name" Nothing
   :: AForm sub master (Day -> Text -> Text -> Text -> Person)

我们可以将 using<*>与产生 a 的东西结合起来Day,得到产生 a 的东西,(Text -> Text -> Text -> Person)依此类推,直到我们得到产生 a 的东西Person。(这一切都有效,因为->关联到右侧和<$>关联<*>到左侧,就像这样$做一样。)

于 2013-04-08T02:32:43.143 回答