应用程序:<$> 与 pure 和 <*>

Applicatives: <$> vs. pure and <*>

在尝试了一段时间的示例后,对我来说,在处理 Control.Applicative 类型 class.[=15 时,myFunction <$>pure myFunction <*> 看起来是等价的=]

示例:

(++) <$> Just "foo" <*> Just "bar"
pure (++) <*> Just "foo" <*> Just "bar"

两者都产生 Just "foobar"

这些确实是等效的还是我忽略了边缘情况? 我更喜欢哪种变体/更常见?虽然 pure 方法更长,但对我来说 Control.Applicative typclass 看起来更笼统和真实。

这需要等效。事实上,在 Applicative typeclass 的文档中,我们读到:

As a consequence of these laws, the Functor instance for f will satisfy

fmap f x = pure f <*> x

因为 (<$>) :: Functor f => (a -> b) -> f a -> f b 是:

An infix synonym for fmap.

因此认为:

f <$> x = pure f <*> x

因此可以使用两者来实现相同的目的。然而,由于 f <$> x 更短,而且可能更快(因为 (<*>) 需要处理所有 f a),因此可能建议使用 (<$>)。此外,作为 liftA2 的默认实现是 liftA2 f x = (<*>) (fmap f x),因此它也使用 fmap(所以 <$>)。

我想在这里简单地插一句,因为我有很多不受欢迎的观点,这就是其中之一,而你这么问 nyaaaaah。

承认:社区似乎或多或少同意 f <$> x <*> y <*> z 风格更好。但我其实更喜欢pure f <*> x <*> y <*> z。我发现以应用风格编写的行往往比较长,因为每个参数本身通常是对函数的调用,所以:

fancyParser = FancyConstructor <$> fooParserWith 7 blag <*> barParserSep "hi" (sizzleParser pop) <*> bazParser
-- OR
fancyParser = pure FancyConstructor <*> fooParserWith 7 blag <*> barParserSep "hi" (sizzleParser pop) <*> bazParser

为了可读性,我经常将论点分成一行;我发现视觉上的分离让参数边界的位置更清晰,让我的眼睛稍微休息一下,并且减少了线条以丑陋的方式换行的可能性:

fancyParser = FancyConstructor
    <$> fooParserWith 7 blag
    <*> barParserSep "hi" (sizzleParser pop)
    <*> bazParser
-- OR
fancyParser = pure FancyConstructor
    <*> fooParserWith 7 blag
    <*> barParserSep "hi" (sizzleParser pop)
    <*> bazParser

在这种形式中,我认为我更喜欢 pure/<*> 的原因很清楚:它使行开头完全一致。一方面,这种一致性在视觉上很吸引人。但更重要的是,当我不可避免地重构 FancyConstructor 以重新排列字段的顺序,或在开头插入一个新字段时,我可以完全不受惩罚地使用逐行编辑命令。某些正则表达式搜索也变得稍微容易一些。它只能消除一点点摩擦……但是消除小摩擦是进行大型编程的一个重要部分。

P.S。我发现方便的其他不受欢迎的获得一致行格式的方法示例:

-- orthodox
foo a b c d
    = f
    . g
    . h

-- dmwit
foo a b c d = id
    . f
    . g
    . h

-- orthodox
foo a b c d =
    [ x
    , y
    , z
    ]

-- dmwit
foo a b c d = tail [undefined
    , x
    , y
    , z
    ]

-- orthodox
bad = die
    $  "some kind of "
    <> "error that is best described "
    <> "on multiple lines"

-- dmwit
bad = die $ mempty {- or "", if appropriate -}
    <> "some kind of "
    <> "error that is best described "
    <> "on multiple lines"

-- orthodox
foo
    :: arg1
    -> arg2
    -> result

-- dmwit
foo ::
    arg1 ->
    arg2 ->
    result

-- orthodox
foo a b c d =
    [ t
    | u <- v
    , w <- x
    , y <- z
    ]

-- dmwit
foo a b c d =
    [ t | _ <- [()]
    , u <- v
    , w <- x
    , y <- z
    ]