【问题标题】:Uniqueness of pure纯粹的独特性
【发布时间】:2021-03-09 22:09:10
【问题描述】:

对于任何Applicative 实例,一旦写入<*>pure 就会被唯一确定。假设您有pure1pure2,两者都遵守法律。那么

pure2 f <*> pure1 y = pure1 ($ y) <*> pure2 f  -- interchange for pure1
pure2 id <*> pure1 y = pure1 ($ y) <*> pure2 id -- specialize f to id
pure1 y = pure1 ($ y) <*> pure2 id  -- identity for pure2
pure1 y = fmap ($ y) (pure2 id) -- applicative/fmap law for pure1
pure1 y = pure2 ($ y) <*> pure2 id -- applicative/fmap law for pure2
pure1 y = pure2 y -- homomorphism law

但是以这种方式使用fmap 法律感觉就像在作弊。有没有办法在不诉诸参数化的情况下避免这种情况?

【问题讨论】:

  • 有趣。我敢肯定,鉴于&lt;*&gt; 并不是真正的应用程序的“基本”方法,即monoidal functors——它是fzip :: f a -&gt; f b -&gt; f (a,b)。但是,我只是不太明白您所说的“感觉像是作弊”的意思。你只是在使用法律,这怎么可能是作弊呢。
  • @leftaroundabout,我想我真正的意思是fmap 的唯一性(这意味着fmap/Applicative 法则)来自参数化。因此,如果您将这些定律排除在参数性成立的背景之外,那么就会产生一个问题:pure 是否仍然由&lt;*&gt; 决定,或者它实际上是否仅由fmap&lt;*&gt; 的组合决定。跨度>
  • 我还想知道fzipfunit :: f () 会如何回答这类问题。

标签: haskell applicative


【解决方案1】:

当前文档中给出的法律确实依赖于参数化来连接到fmap

如果没有参数,我们就会失去这种联系,因为我们甚至无法证明 fmap 的唯一性,而且确实存在 fmap 不是唯一的 System F 的模型/扩展。

打破参数性的一个简单示例是添加类型案例(类型上的模式匹配),这允许您定义以下twist,它检查其参数的类型并翻转它找到的任何布尔值:

twist :: forall a. a -> a
twist @Bool     = not
twist @(a -> b) = \f -> (\x -> twist @b (f (twist @a x)))
twist @a        = id  -- default case

它具有多态身份的类型,但它是not的身份函数。

然后您可以定义以下“扭曲身份”函子,其中puretwist 应用于其参数:

newtype I a = I { runI :: a }

pure :: a -> I a
pure = I . twist

(<*>) :: I (a -> b) -> I a -> I b  -- The usual, no twist
(<*>) (I f) (I x) = I (f x)

twist 的一个关键属性是twist . twist = id。这允许它在组合使用pure 嵌入的值时自行抵消,从而保证the four laws of Control.Applicative。 (Proof sketch in Coq)

这个扭曲的函子也产生了fmap 的不同定义,如\u -&gt; pure f &lt;*&gt; u。展开定义:

fmap :: (a -> b) -> I a -> I b
fmap f (I x) = I (twist (f (twist x)))

这与 duplode 的回答并不矛盾,它将关于幺半群身份唯一性的通常论点转换为幺半群函子的设置,但它破坏了它的方法。问题是该视图假设您已经有一个给定的函子,并且单曲面结构与它兼容。特别是,将pure 定义为\x -&gt; fmap (const x) funit (以及(&lt;*&gt;) 也相应地)隐含了法律fmap f u = pure f &lt;*&gt; u。如果您一开始就没有修复fmap,那么这个论点就会失效,因此您没有任何可依赖的一致性法则。

【讨论】:

  • 这是一个很好的参数化破坏示例。当我最初写我的答案时,我希望为不完全参数化的仿函数保留一个开口,但它仍然具有独特的映射函数。不过,funit/pure 连接并不能很好地转换为此类设置。
  • " type-case (pattern-matching on types)" 与将twist 设为类/方法并使用instances 实现类型上的模式匹配有什么不同?
  • 你的twist . twist = id类似于双reverse,其中(reverse . f) . (reverse . g) =/= (reverse . (f . g))在这里stackoverflow.com/questions/66135623/…
  • 您可以使用一个类来定义类似的东西,但这似乎超出了关于参数性和 Applicative 类的问题的重点。
  • 是的,我写这句话主要是为了说“有人至少做了一次证明”,以避免让那些想亲眼看到证明的人胡思乱想。
【解决方案2】:

让我们切换到 applicative 的 monoidal 函子表示:

funit :: F ()
fzip :: (F a, F b) -> F (a, b)

fzip (funit, v) ~ v
fzip (u, funit) ~ u
fzip (u, fzip (v, w)) ~ fzip (fzip (u, v), w)

如果我们将ab 专门化为()(并查看元组同构),定律告诉我们funitfzip 指定了一个幺半群。由于幺半群的身份是唯一的,funit 是唯一的。对于通常的Applicative 类,pure 然后可以恢复为...

pure a = fmap (const a) funit

... 这也是独一无二的。虽然原则上尝试将此推理扩展到至少一些非完全参数化的函子是有意义的,但这样做可能需要在很多地方做出妥协:

  • () 作为对象的可用性,定义funit 并陈述幺半群函子定律;

  • 用于定义 pure 的 map 函数的唯一性(另请参阅 Li-yao Xia's answer),或者,如果不这样做,则以某种方式唯一指定 fmap . const 类似物的明智方法;

  • 函数类型作为对象的可用性,为了用(&lt;*&gt;) 来说明Aplicative 法则。

【讨论】:

  • 听起来可能需要Functor 实例来链接这些演示文稿?
  • @dfeuer 也许这可以避免。例如,它应该Data.Set.map . const 而不是(&lt;$) 一起工作。我想,“应该”背后的问题是,如果我们不能依赖参数化,究竟需要什么才能有一个合理的 (&lt;$) 类似物。 (顺便说一句,请注意,再想一想,我在某种程度上削弱了答案的结束语。)
  • @dfeuer 在任何情况下,Data.Set 是一个有点可疑的例子,因为坚持Ord 约束会使我们直接类似于(&lt;*&gt;)。似乎我能想到的大多数例子都以某种方式存在问题......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-14
  • 2014-08-17
  • 1970-01-01
  • 2016-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多