有一个`(a - > b) - > b`相当于有一个`a`?

时间:2017-07-24 18:50:29

标签: haskell functional-programming purely-functional

在纯函数式语言中,你唯一能用值做的就是对它应用一个函数。

换句话说,如果你想用类型为a的值做任何有趣的事情,你需要一个类型为f :: a -> b的函数(例如),然后应用它。如果有人向您(flip apply) a提供(a -> b) -> b类型,那么它是a的合适替代品吗?

你会怎样称呼(a -> b) -> b类型的东西?看来它似乎是a的替身,我很想将其称为代理,或来自http://www.thesaurus.com/browse/proxy的内容。

3 个答案:

答案 0 :(得分:44)

luqui的回答非常好,但我会提出forall b. (a -> b) -> b === a的另一个解释有几个原因:首先,因为我认为对Codensity的概括有点过于热情。第二,因为这是将一堆有趣的东西联系在一起的机会。起!

z5h的魔术盒

想象一下,有人翻了一个硬币然后把它放在一个魔术盒里。您无法在框内看到,但如果您选择类型b并将框中的Bool -> b类型的函数传递给该框,则该框会吐出b。如果不深入了解这个盒子我们能学到什么?我们可以了解硬币的状态吗?我们可以了解框用于生成b的机制吗?事实证明,我们可以做到这两点。

我们可以将该框定义为Box Bool类型的 rank 2 函数,其中

type Box a = forall b. (a -> b) -> b

(此处,排名2类型表示盒子制造商选择a,而盒子用户选择b。)

我们将a放在框中,然后关闭框,创建一个闭包

-- Put the a in the box.
box :: a -> Box a
box a f = f a

例如,box True。部分应用程序只是创建闭包的一种聪明方法!

现在,硬币头还是尾巴?由于我,盒子用户,我可以选择b,我可以选择Bool并传入函数Bool -> Bool。如果我选择id :: Bool -> Bool,那么问题是:该框会吐出它包含的值吗?答案是该框会吐出它包含的值,或者它会吐出废话(底部值如undefined)。换句话说,如果你得到答案,那么答案必须是正确的。

-- Get the a out of the box.
unbox :: Box a -> a
unbox f = f id

因为我们不能在Haskell中生成任意值,所以盒子唯一能做的就是将给定的函数应用于它隐藏的值。这是参数多态的结果,也称为参数化

现在,为了表明Box aa是同构的,我们需要证明有关装箱和拆箱的两件事。我们需要证明你得到了你投入的东西,并且你可以把你得到的东西放进去。

unbox . box = id
box . unbox = id

我会做第一个并留下第二个作为读者的练习。

  unbox . box
= {- definition of (.) -}
  \b -> unbox (box b)
= {- definition of unbox and (f a) b = f a b -}
  \b -> box b id
= {- definition of box -}
  \b -> id b
= {- definition of id -}
  \b -> b
= {- definition of id, backwards -}
  id

(如果这些证明看起来相当微不足道,那是因为Haskell中的所有(全部)多态函数都是自然变换,而我们在这里证明的自然性。参数化再一次为我们提供了低定理,价格低廉!)

作为读者的另一个练习,为什么我不能用rebox实际定义(.)

rebox = box . unbox

为什么我必须像某种洞穴人一样内联(.)的定义?

rebox :: Box a -> Box a
rebox f = box (unbox f)

(提示:boxunbox(.)的类型是什么?)

身份和密度和Yoneda,哦,我的!

现在,我们如何概括Box? luqui使用Codensity:两个b都由一个任意类型的构造函数推广,我们称之为f。这是f a的密码transform

type CodenseBox f a = forall b. (a -> f b) -> f b

如果我们修复了f ~ Identity,那么我们会回来Box。但是,还有另一种选择:我们只能点击f的返回类型:

type YonedaBox f a = forall b. (a -> b) -> f b

(我在这里用这个名字给出了游戏,但我们会回到那里。)我们也可以在这里修复f ~ Identity以恢复Box,但我们让这个盒子用户传递正常功能而不是Kleisli箭头。为了理解我们正在概括的 ,让我们再看一下box的定义:

box a f = f a

嗯,这只是flip ($),不是吗?事实证明,我们的其他两个框是通过推广($)而构建的:CodenseBox是部分应用的翻转monadic绑定,YonedaBox是部分应用的flip fmap。 (这也解释了为什么Codensity fMonadYoneda fFunctor 任何选择f:唯一的方法创建一个是分别关闭一个bind或fmap。)此外,这两个深奥的类别理论概念都是对许多工作程序员熟悉的概念的概括:CPS转换!

换句话说,YonedaBox是Yoneda嵌入,box正确抽象的unbox / YonedaBox法则是Yoneda引理的证明!

<强> TL; DR:

forall b. (a -> b) -> b === a是Yoneda Lemma的一个实例。

答案 1 :(得分:27)

这个问题是一个了解更多深层概念的窗口。

首先,请注意这个问题存在歧义。我们是指类型forall b. (a -> b) -> b,以便我们可以使用我们喜欢的任何类型来实例化b,或者我们是指(a -> b) -> b我们无法选择的特定b。 / p>

我们可以在Haskell中形式化这种区别:

newtype Cont b a = Cont ((a -> b) -> b)
newtype Cod a    = Cod (forall b. (a -> b) -> b)

这里我们看到一些词汇。第一种类型是Cont monad,第二种类型是Codensity Identity,虽然我对后一种术语的熟悉程度不足以说明你应该用英语称什么。

Cont b a不能等同于a,除非a -> b至少能保存与a一样多的信息(请参阅下面的Dan Robertson的评论)。因此,例如,请注意,您永远无法从Cont Void a中获取任何内容。

Cod a相当于a。要看到这一点就足以见证同构:

toCod :: a -> Cod a
fromCod :: Cod a -> a

我将作为练习留下的实现。如果你想真正做到这一点,你可以尝试证明这对真的是一个同构。 fromCod . toCod = id很简单,但toCod . fromCod = id要求Cod $(document).ready(function() { $('#txt').click(function () { var requestData = $('#txtid').val(); var url = '<my api url>' + requestData; $('#resultDiv1').dataTable({ "processing": true, "ajax": url, "columns": [ {"": "account.id"}, {"": "account.rel"}, {"": "account.fin"}, {"": "account.date"} ], "dom": "Bfrtip", "buttons": [ 'copy', 'csv', 'excel', 'pdf', 'print' ] }); }); });

答案 2 :(得分:12)

其他答案在描述类型forall b . (a -> b) -> ba之间的关系方面做得很好,但我想指出一个警告,因为它会导致一些有趣的开放性问题,我一直在努力。

从技术上讲,forall b . (a -> b) -> ba不同构,其中(1)允许您编写一个不会终止的表达式( 2)是按值调用(严格)或包含seq。我的观点是是挑剔的,或者表明Haskell中的参数化被削弱(众所周知),但是可能有很好的方法来强化它,并且在某种意义上回收像这样的同构。

有些forall b . (a -> b) -> b类型的字词无法表示为a。为了了解原因,让我们先看看Rein留下的证据,box . unbox = id。事实证明,这个证明实际上比他答案中的证据更有趣,因为它以一种关键的方式依赖于参数化。

box . unbox
= {- definition of (.) -}
  \m -> box (unbox m)
= {- definition of box -}
  \m f -> f (unbox m)
= {- definition of unbox -}
  \m f -> f (m id)
= {- free theorem: f (m id) = m f -}
  \m f -> m f
= {- eta: (\f -> m f) = m -}
  \m -> m
= {- definition of id, backwards -}
  id

参与性发挥作用的有趣步骤是应用自由定理 f (m id) = m f。此属性是forall b . (a -> b) -> b的结果,m的类型。如果我们将m视为内部具有类型a的基础值的框,那么m唯一可以对其参数执行的操作是将其应用于此基础值并返回结果。在左侧,这意味着f (m id)从框中提取基础值,并将其传递给f。在右侧,这意味着mf直接应用于基础值。

不幸的是,当我们使用下面的mf这样的字词时,这种推理并不完全适用。

m :: (Bool -> b) -> b
m k = seq (k true) (k false)

f :: Bool -> Int
f x = if x then ⊥ else 2`

回想一下,我们想展示f (m id) = m f

f (m id)
= {- definition f -}
  if (m id) then ⊥ else 2
= {- definition of m -}
  if (seq (id true) (id false)) then ⊥ else 2
= {- definition of id -}
  if (seq true (id false)) then ⊥ else 2
= {- definition of seq -}
  if (id false) then ⊥ else 2
= {- definition of id -}
  if false then ⊥ else 2
= {- definition of if -}
  2

m f
= {- definition of m -}
  seq (f true) (f false)
= {- definition of f -}
  seq (if true then ⊥ else 2) (f false)
= {- definition of if -}
  seq ⊥ (f false)
= {- definition of seq -}
  ⊥

显然2不等于所以我们丢失了我们的自由定理以及a(a -> b) -> b之间的同构。但到底发生了什么?从本质上讲,m不仅仅是一个表现良好的盒子,因为它将其参数应用于两个不同的基础值(并使用seq来确保这两个应用程序都被实际评估),我们可以观察到通过传递一个延续,该延续终止于其中一个基础值,而不是另一个。换句话说,m id = false并非真正忠实地代表m作为Bool,因为它会忘记&#39}。 m使用 truefalse调用其输入的事实。

问题是三件事之间相互作用的结果:

  1. 存在不定期。
  2. seq。
  3. 的存在
  4. 类型forall b . (a -> b) -> b的条款可能会多次应用其输入。
  5. 没有希望绕过第1或第2点。Linear types可能会给我们一个机会来解决第三个问题。 a ⊸ b类型的线性函数是从类型a到类型b的函数,它必须使用其输入一次。如果我们要求m具有类型forall b . (a -> b) ⊸ b,那么这将排除我们对自由定理的反例,并且应该让我们展示aforall b . (a -> b) ⊸ b 之间的同构即使存在不确定和seq

    这真的很酷!它表明线性有能力拯救&#39;通过驯服效果的有趣属性可以使真正的等式推理变得困难。

    但仍然存在一个重大问题。我们还没有技术来证明forall b . (a -> b) ⊸ b类型所需的自由定理。事实证明,当前的逻辑关系(我们通常用来做这种证明的工具)并没有被设计成以所需的方式考虑线性。此问题对于为执行CPS转换的编译器建立正确性具有意义。