【问题标题】:Understanding Data.Functor.Constant constructor and applicative laws理解 Data.Functor.Constant 构造函数和应用规律
【发布时间】:2014-02-05 20:11:57
【问题描述】:

我对 Data.Functor.Constant 的类型构造函数以及它如何与 applicative 一起使用感到困惑。


首先是构造函数:

当我检查Constant :: a -> Constant a b的类型时

我看到它需要一个a,但返回一个Constant a b

b 来自哪里,为什么存在?


其次,我在 Applicative 上苦苦挣扎:

我了解 Constant 需要在内部有一个 Monoid 才能成为 Applicative 实例。

它必须遵守的一条法律是:pure id <*> Constant x = x

我还以为是一样的:Constant id <*> Constant x = x

但我想我错了,因为下面的代码清楚地显示了纯粹的不同行为。

:t pure id <*> Constant "hello" // Constant [Char] b

:t Constant id <*> Constant "hello" // Couldn't match expected type `a0 -> a0' with actual type `[Char]'

:t pure id <*> Constant reverse //  Constant ([a] -> [a]) b

:t Constant id <*> Constant reverse // Constant ([a] -> [a]) b

我发现它只有在 x 是同一个幺半群时才有效,除非我使用纯。所以我不确定为什么 pure 的工作方式不同。我怀疑这与b 有关,这就是他们在同一个问题中的原因。

总结两个问题:

  1. b 在 Constant 构造函数中做了什么?

  2. 为什么即使幺半群内部不同,pure 也能工作?

非常感谢!

【问题讨论】:

    标签: haskell constants functor applicative monoids


    【解决方案1】:

    好的,所以你有这个类型

    data Const a b = Const { getConst :: a }
    

    您的第一个问题是b 来自哪里?”

    答案是它并非来自任何地方。就像您可以将Maybe b 视为一个容器,它包含0 个或1 个b 类型的值,Const a b 是一个容器,它恰好包含0 个类型为b 的值(但确实包含a) 类型的值。

    您的第二个问题是“为什么会在那里?”

    嗯,有时候有一个函子说它可能包含b 类型的值,但实际上包含其他东西(例如,想想Either a b 函子——不同之处在于Either a b 可能拥有b类型的值,而Const a b绝对没有)。

    然后您询问了代码 sn-ps pure id &lt;*&gt; Const "hello"Const id &lt;*&gt; Const "hello"。你以为这些是一样的,但事实并非如此。原因是ConstApplicative 实例看起来像

    instance Monoid m => Applicative (Const m) where
      -- pure :: a -> Const m a
      pure _ = Const mempty
    
      -- <*> :: Const m (a -> b) -> Const m a -> Const m b
      Const m1 <*> Const m2 = Const (m1 <> m2)
    

    由于实际上没有任何值具有第二个参数的类型,我们只需要处理那些具有第一个参数类型的值,我们知道它是一个幺半群。这就是为什么我们可以让Const 成为Applicative 的实例——我们需要从某处提取m 类型的值,而Monoid 实例为我们提供了一种从无到有的方法(使用mempty )。

    那么在您的示例中发生了什么?你有pure id &lt;*&gt; Const "hello",它必须有类型Const String a,因为id :: a -&gt; a。在这种情况下,幺半群是String。我们有mempty = "" 用于String(&lt;&gt;) = (++)。所以你最终得到了

    pure id <*> Const "hello" = Const "" <*> Const "hello"
                              = Const ("" <> "hello")
                              = Const ("" ++ "hello")
                              = Const "hello"
    

    另一方面,当您编写Const id &lt;*&gt; Const "hello" 时,左侧参数的类型为Const (a -&gt; a) b,右侧的类型为Const String b,您会看到类型不匹配,这就是您获得类型的原因错误。

    现在,为什么这很有用? lens 库中有一个应用程序,它允许您在纯函数设置中使用 getter 和 setter(熟悉命令式编程)。镜头的简单定义是

    type Lens b a = forall f. Functor f => (a -> f a) -> (b -> f b)
    

    即如果你给它一个转换a类型值的函数,它会给你一个转换b类型值的函数。那有什么用?好吧,让我们为特定函子 f 选择一个类型为 a -&gt; f a 的随机函数。如果我们选择Identity仿函数,它看起来像

    data Identity a = Identity { getIdentity :: a }
    

    那么如果l是一个镜头,那么定义

    modify :: Lens b a -> (a -> a) -> (b -> b)
    modify l f = runIdentity . l (Identity . f)
    

    为您提供了一种方法来获取转换 as 的函数并将它们转换为转换 bs 的函数。

    我们可以传入的另一个a -&gt; f a 类型的函数是Const :: a -&gt; Const a a(请注意,我们已经特化了第二个类型与第一个类型相同)。那么镜头l的作用就是把它变成b -&gt; Const a b类型的函数,这告诉我们它可能包含一个b,但实际上偷偷摸摸它确实包含一个@987654371 @!一旦我们将它应用于b 类型的东西以获得Const a b,我们就可以使用getConst :: Const a b -&gt; a 来提取a 类型的值。所以这给了我们一种从b 中提取a 类型值的方法——即它是一个getter。定义看起来像

    get :: Lens b a -> b -> a
    get l = getConst . l Const
    

    作为一个镜头的例子,你可以定义

    first :: Lens (a,b) a
    first f (a,b) = fmap (\x -> (x,b)) (f a)
    

    这样你就可以打开一个 GHCI 会话并编写

    >> get first (1,2)
    1
    >> modify first (*2) (3,4)
    (6,4)
    

    正如您想象的那样,它在各种情况下都很有用。

    【讨论】:

    • 很好的答案,虽然我认为说“Const a b撒谎关于持有b 类型的值”是误导。也许“Either a b 可能拥有 b 类型的值,而 Const a b 绝对没有。”?
    • @TomEllis 好建议,已编辑包含它。也希望得到有关我对 lens 解释的反馈,这是我第一次尝试解释它。
    • 我只是链接到 SPJ 的视频解释它。好吧,也许另外链接到它并说“这是对此的另一种奇妙的解释。”
    • 太棒了!谢谢你。我不敢相信我没有意识到 Const 部分应用于 mempty。这很有意义。确实,镜头是我首先看这个的动机。非常感谢。
    • first的类型应该是Lens (a,b) a
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 2020-12-29
    • 2014-10-07
    • 2019-10-23
    • 2015-01-07
    • 1970-01-01
    相关资源
    最近更新 更多