首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >为什么函子实现是可能的?

为什么函子实现是可能的?
EN

Stack Overflow用户
提问于 2018-02-05 09:19:55
回答 3查看 247关注 0票数 7

我阅读了https://www.schoolofhaskell.com/user/commercial/content/covariance-contravariance关于正负位置一节的文章,有一个例子:

代码语言:javascript
代码运行次数:0
运行
复制
newtype Callback a = Callback ((a -> IO ()) -> IO ())

它在a上是协变还是反变?

就是问题所在。

其解释是:

但是现在,我们通过:(a -> IO ()) -> IO ()将整个函数包装为一个新函数的输入。作为一个整体,这个函数是使用一个Int,还是生成一个Int?为了获得直觉,让我们看一下随机数的Callback Int实现: supplyRandom ::回调Int supplyRandom =回调$ \f -> do int <- randomRIO (1,10) f int 从这个实现中可以清楚地看到,supplyRandom实际上正在生成一个Int。这类似于Maybe,这意味着我们有一个坚实的论据来证明它也是协变的。那么,让我们回到正负术语,看看它是否解释了原因。

对于我来说,函数supplyRandom生成Int,同时,它使用Int f int。我不明白,为什么作者的意思是,它只产生一个Int.

一位提交人进一步解释了以下情况:

a -> IO ()中,a处于负位置。在(a -> IO ()) -> IO ()中,a -> IO ()处于负位置。现在我们只需遵循乘法规则:当你把两个负数相乘时,你得到了一个正数。因此,在(a -> IO ())-> IO ()中,a处于正位置,这意味着回调在a上是协变的,我们可以定义一个函子实例。事实上,GHC同意我们的观点。

我理解这个解释,但我不明白为什么a是正面的,为什么它是协变的。

考虑函子的定义:

代码语言:javascript
代码运行次数:0
运行
复制
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b

如何将(a -> IO ())-> IO ()中的类型变量(a -> IO ())-> IO ()转换为(b -> IO ())-> IO ()?我想,我误解了这个概念。

查看函子实现:

代码语言:javascript
代码运行次数:0
运行
复制
newtype Callback a = Callback
    { runCallback :: (a -> IO ()) -> IO ()
    }

instance Functor Callback where
    fmap f (Callback g) = Callback $ \h -> g (h . f)

目前还不清楚a -> b的转换是在哪里进行的。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-02-05 10:56:11

对于我来说,函数supplyRandom生成Int,同时,它使用Int f int。我不明白,为什么作者的意思是,它只产生一个Int.

实际上,在int <- randomRIO (1, 10)行中,randomRIO正在生成Int,而supplyRandom正在消耗它。类似地,在f int行中,生成(即提供) Int的是supplyRandom,消费Int的是f

当我们说生产和消费时,我们实际上只是指给予和索取。生产并不一定意味着凭空生产,尽管这也是可能的。例如:

代码语言:javascript
代码运行次数:0
运行
复制
produceIntOutOfThinAir :: Callback Int
produceIntOutOfThinAir = Callback $ \f -> f 42 -- produced 42 out of thin air

在作者的例子中,supplyRandom不会凭空产生Int。相反,它需要randomRIO生成的randomRIO,然后将Int提供给f。那完全没问题。

supplyRandom的类型签名(即打开包装时的(Int -> IO ()) -> IO () )只告诉我们supplyRandom生成一些Int。它没有指定必须如何生成Int

原始答案:

让我们来看看fmapFunctor Callback类型

代码语言:javascript
代码运行次数:0
运行
复制
fmap :: (a -> b) -> Callback a -> Callback b

让我们将Callback替换为它的未包装类型:

代码语言:javascript
代码运行次数:0
运行
复制
                           Callback a                Callback b
                     __________|__________      _________|_________
                    |                     |    |                   |
fmap :: (a -> b) -> ((a -> IO ()) -> IO ()) -> (b -> IO ()) -> IO ()
        |______|    |_____________________|    |__________|
           |                   |                    |
           f                   g                    h

如您所见,fmap接受三个输入,并需要生成一个类型为IO ()的值。

代码语言:javascript
代码运行次数:0
运行
复制
f :: a -> b
g :: (a -> IO ()) -> IO ()
h :: b -> IO ()
--------------------------
IO ()

这是我们的目标的视觉表现。这条线上的每一件事都是我们的背景(即我们的假设,或者我们知道的东西)。这条线下的每一件事都是我们的目标(即我们试图用我们的假设来证明的东西)。就Haskell代码而言,可以将其编写为:

代码语言:javascript
代码运行次数:0
运行
复制
fmap f g h = (undefined :: IO ()) -- goal 1

如您所见,我们需要使用输入fgh来生成类型为IO ()的值。目前,我正在返回undefined。您可以将undefined视为实际值的占位符(即填空)。那么,我们该如何填补这个空白呢?我们有两个选择。我们既可以应用g,也可以应用h,因为它们都返回IO ()。假设我们决定应用h

代码语言:javascript
代码运行次数:0
运行
复制
fmap f g h = h (undefined :: b) -- goal 2

如您所见,需要将h应用于b类型的值。因此,我们的新目标是b。我们如何填补新的空白?在我们的上下文中,生成b类型值的唯一函数是f

代码语言:javascript
代码运行次数:0
运行
复制
fmap f g h = h (f (undefined :: a)) -- goal 3

但是,我们现在必须生成一个a类型的值,我们既没有a类型的值,也没有生成a类型值的任何函数。因此,应用h不是一种选择。回到目标1,我们的另一个选择是应用g。所以,让我们来试一试:

代码语言:javascript
代码运行次数:0
运行
复制
fmap f g h = g (undefined :: a -> IO ()) -- goal 4

我们的新目标是a -> IO ()a -> IO ()类型的值是什么样子的?因为它是一个函数,我们知道它看起来像一个lambda:

代码语言:javascript
代码运行次数:0
运行
复制
fmap f g h = g (\x -> (undefined :: IO ())) -- goal 5

我们的新目标再次是IO ()。看来我们又回到第一步了,但是等等.有些事不一样。我们的上下文是不同的,因为我们引入了一个新的值x :: a

代码语言:javascript
代码运行次数:0
运行
复制
f :: a -> b
g :: (a -> IO ()) -> IO ()
h :: b -> IO ()
x :: a
--------------------------
IO ()

这个值x从何而来?好像我们是凭空拔出来的,对吧?不,我们不是凭空把它拉出来的。x值来自g。您知道,a类型在g中是协变的,这意味着g生成a。实际上,当我们创建lambda来填补目标4的空白时,我们在上下文中引入了一个新的变量x,它从g中获得了它的值,不管它是什么。

无论如何,我们再次需要生成一个类型为IO ()的值,但是现在我们可以回到选项1(即应用h),因为我们最终有一个a类型的值。我们不想回到选项2(即应用g),因为那时我们只是在循环运行。备选方案1是我们的出路:

代码语言:javascript
代码运行次数:0
运行
复制
fmap f g h = g (\x -> h (undefined :: b)) -- goal 6

fmap f g h = g (\x -> h (f (undefined :: a))) -- goal 7

fmap f g h = g (\x -> h (f x)) -- goal proved

如您所见,\x -> h (f x)只是h . f (即函数组合),其余的是newtype的打包和解压。因此,实际职能被定义为:

代码语言:javascript
代码运行次数:0
运行
复制
fmap f (Callback g) = Callback $ \h -> g (h . f)

希望这解释了为什么a(a -> IO ()) -> IO ()中是协变的。因此,可以定义一个Functor实例的Callback

票数 8
EN

Stack Overflow用户

发布于 2018-02-05 10:33:52

a -> IO ()类型的函数是一个需要a的值:如果没有a,就不能使用这个值。听起来你已经知道这一点了,但需要重复一遍,以使下一个问题更加清晰。

那么,Callback a呢?它是一个愿意对a -> IO ()类型的值进行操作的函数吗?它可以对这样一个值进行操作的唯一方法是传递它一些它可以访问的a:这正是我们在上一段中建立的。所以,虽然您不知道它是如何产生这个a的,但是它必须能够以某种方式产生一个,否则它就不能用它的a -> IO ()做任何事情。

因此,您可以在该fmap之上创建一个a,生成一个b,并生成一个Callback b,这个值可以与任何b -> IO ()一起工作。

票数 2
EN

Stack Overflow用户

发布于 2018-02-05 11:18:57

所以我们有这个:

代码语言:javascript
代码运行次数:0
运行
复制
newtype Callback a = Callback
    { runCallback :: (a -> IO ()) -> IO ()
    }

让我们暂时去掉新的类型并对函数进行操作。

给定(a -> IO ()) -> IO ()类型的函数和a->b类型的函数,我们需要生成一个((b -> IO ()) -> IO ())类型的函数。我们怎么能这么做?让我们试试:

代码语言:javascript
代码运行次数:0
运行
复制
  transformCallback :: (a->b) -> ((a -> IO ()) -> IO ()) -> ((b -> IO ()) -> IO ())
  transformCallback f g = ????

因此,结果回调(我们用?表示的表达式)应该接受b -> IO ()类型的函数,并返回一个IO ()

代码语言:javascript
代码运行次数:0
运行
复制
  transformCallback f g = \h -> ????

很好,现在我们有一个类型为f的函数a->b,一个类型为b->IO ()的函数h,以及类型((a->IO()) -> IO())的原始回调g。我们能用这些做什么?唯一可行的方法似乎是将fh结合起来,以获得a->IO()类型的东西。

代码语言:javascript
代码运行次数:0
运行
复制
 transformCallback f g = \h -> ??? h . f ???

很好,我们有一些类型为a->IO(),而g则接受该类型并返回IO (),这正是我们应该返回的内容。

代码语言:javascript
代码运行次数:0
运行
复制
 transformCallback f g = \h -> g ( h . f )

那么f在哪里被调用呢?我们要喂它什么?

回想一下,原始回调的类型是(a -> IO ()) -> IO ()。我们可以问,这个(a -> IO ())函数在哪里调用?喂它的是什么?

首先,它不需要被调用。回调很可能忽略它,独立地生成一个IO()。但是如果它被调用,回调就会调用它,并且它会从某个地方获取一个a来传递给那个a->IO()。这是足够重要的重复:回调生成一个a并将其输入到它的参数。

现在,如果我们将原始回调(一个将a转换为b,然后将结果传递给b->IO类型的函数)的函数提供,则回调函数与其他类型的a->IO函数一样乐于使用它。现在和以前一样,回调生成一个a并将其输入到它的参数,参数将其转换为b,然后生成一个IO,一切都按其应有的方式继续进行。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48619201

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档