应用:<$> vs. pure 和 <*>

sch*_*uch 6 haskell applicative

尝试例子了一会儿,对我之后,它看起来像myFunction <$>pure myFunction <*>在Control.Applicative型类时是等价的。

例子:

(++) <$> Just "foo" <*> Just "bar"
pure (++) <*> Just "foo" <*> Just "bar"
Run Code Online (Sandbox Code Playgroud)

两者产量Just "foobar"

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

Wil*_*sem 10

这需要等价。事实上,在Applicativetypeclass的文档中,我们读到:

作为这些定律的结果, 的Functor实例f将满足

fmap f x = pure f <*> x
Run Code Online (Sandbox Code Playgroud)

由于(<$>) :: Functor f => (a -> b) -> f a -> f b是:

的中缀同义词fmap

因此认为:

f <$> x = pure f <*> x
Run Code Online (Sandbox Code Playgroud)

因此,可以使用两者来实现相同的目的。f <$> x然而,由于更短,并且可能更快(因为(<*>)需要处理所有f as),因此可能建议使用(<$>))。此外,正如@chepner 所说,默认实现liftA2liftA2 f x = (<*>) (fmap f x),所以这也使用fmap(so <$>)。

  • 另外,GHC 中“liftA2”的默认定义是“liftA2 fx = (&lt;*&gt;) (fmap fx)”。 (2认同)

Dan*_*ner 5

我只想在这里简单地插一句,因为我持有许多不受欢迎的意见,这就是其中之一,你这么问了 nyaaaaah。

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

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

为了可读性,我经常将论点拆分成自己的行;我发现视觉分离使参数边界的位置更清晰,让我的眼睛休息一下,并且不太可能以丑陋的方式包裹线:

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

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

PS其他不受欢迎的获得一致行格式的方法示例,我发现它们很方便:

-- 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
    ]
Run Code Online (Sandbox Code Playgroud)