Loading [MathJax]/jax/input/TeX/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >为什么函子实现是可能的?

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
运行
AI代码解释
复制
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b

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

查看函子实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 02:56:11

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
运行
AI代码解释
复制
fmap :: (a -> b) -> Callback a -> Callback b

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
运行
AI代码解释
复制
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
运行
AI代码解释
复制
fmap f (Callback g) = Callback $ \h -> g (h . f)

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

票数 8
EN

Stack Overflow用户

发布于 2018-02-05 02: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 03:18:57

所以我们有这个:

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

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

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

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

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

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

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 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

复制
相关文章
python 将读取的数据写入txt文件_c中怎样将数据写入txt文件
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
全栈程序员站长
2022/11/09
6.4K0
Python将数据写入txt文件_python将内容写入txt文件
readlines() 会把每一行的数据作为一个元素放在列表中返回,读取所有行的数据
全栈程序员站长
2022/11/11
12.5K0
Numpy中的数组维度
在对 np.arange(24)(0, 1, 2, 3, ..., 23) 进行重新的排列时,在多维数组的多个轴的方向上,先分配最后一个轴(对于二维数组,即先分配行的方向,对于三维数组即先分配平面的方向)
benym
2022/07/14
1.6K0
Python中numpy数组切片
Python中符合切片并且常用的有:列表,字符串,元组。 下面那列表来说明,其他的也是一样的。 格式:[开头:结束:步长] 开头:当步长>0时,不写默认0。当步长<0时,不写默认-1 结束:当步长>0时,不写默认列表长度加一。当步长<0时,不写默认负的列表长度减一 步长:默认1,>0 是从左往右走,<0是从右往左走 遵循左闭右开原则,如:[0:9]等价于数学中的[0,9)
狼啸风云
2020/12/18
3.3K0
Python中numpy数组切片
python读取txt文件中的数组
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/139543.html原文链接:https://javaforall.cn
全栈程序员站长
2022/08/23
4K0
Python将图片输出为二维数组并保存到txt中
使用Python将图片输出为二维数组,并保存到txt文件中。代码如下:# coding=utf8from PIL import Imageimport numpy as npfrom scipy import miscimport matplotlib.pyplot as pyplot #读图片def loadImage(): im = Image.open("0001.jpg") #读取图片 im.show() #显示原图 im = im.c
代码的路
2022/06/18
2.1K0
如何将NumPy数组保存到文件中以进行机器学习
祝大家新年快乐,今天看到的文章然后就翻译了一下,涉及到的技术点都很简单,算是一篇水文,而且我对文章的改动比较大,但是还希望能给你带来一点帮助。
PM小王
2020/01/15
7.8K0
将三维数组中的同名的键拆分成三维数组的每个数组中包括原来不同的二维数组的键…
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/112640.html原文链接:https://javaforall.cn
全栈程序员站长
2022/07/08
1.8K0
将三维数组中的同名的键拆分成三维数组的每个数组中包括原来不同的二维数组的键…
numpy中数组的遍历技巧
在numpy中,当需要循环处理数组中的元素时,能用内置通函数实现的肯定首选通函数,只有当没有可用的通函数的情况下,再来手动进行遍历,遍历的方法有以下几种
生信修炼手册
2020/06/17
12.5K0
Taro中如何将store加载到项目中
上面文章我们了解了如何创建store,最后导出时,在函数内部创建了store,所以导出时,函数需要调用,然后通过provicer组件将其注入到项目中。
挥刀北上
2022/05/11
7650
Taro中如何将store加载到项目中
numpy中的掩码数组
numpy中有一个掩码数组的概念,需要通过子模块numpy.ma来创建,基本的创建方式如下
生信修炼手册
2020/06/10
1.9K0
将多个txt文件中的内容写在一个txt中的方法
import os filename='./train_data/img_' for i in range(1,19736): newfile=filename+str(i)+'.txt' if os.path.exists(newfile): read= open(newfile, 'r') for line in read: writ = open('recode.txt', 'a') writ.write(
陶陶name
2022/05/13
3.4K0
python 将一个txt文件数据,按要求分开,写入多个txt文本中
matinal
2023/10/13
2780
Numpy数组
一、NumPy简介 NumPy是针对多维数组(Ndarray)的一个科学计算(各种运算)包,封装了多个可以用于数组间计算的函数。 数组是相同数据类型的元素按一定顺序排列的组合,注意必须是相同数据类型的,比如说全是整数、全是字符串等。 array([1,2,3]) # 数值型数组 array(['w','s','q'],dtype = '<U1') # 字符型数组 二、NumPy 数组的生成 要使用 NumPy,要先有符合NumPy数组的数据,不同的包
见贤思齊
2020/08/05
4.9K0
Numpy数组
Numpy 中 axis = n 对应 ndarray 的第 nnn 层 [],从最外层的 axis = 0,逐渐往内层递增。
hotarugali
2022/03/03
7890
Numpy数组
Python 将数据写入文件(txt、csv、excel)
一、将列表数据写入txt、csv、excel 1、写入txt def text_save(filename, data):#filename为写入CSV文件的路径,data为要写入数据列表. file = open(filename,'a') for i in range(len(data)): s = str(data[i]).replace('[','').replace(']','')#去除[],这两行按数据不同,可以选择 s = s.replace
菲宇
2019/07/31
41.3K0
numpy中数组操作的相关函数
在numpy中,有一系列对数组进行操作的函数,在使用这些函数之前,必须先了解以下两个基本概念
生信修炼手册
2020/06/17
2.1K0
Python中numpy数组的拼接、合并
np.append() np.concatenate() np.stack() np.hstack() np.vstack() np.dstack() 其中最泛用的是第一个和第二个。第一个可读性好,比较灵活,但是占内存大。第二个则没有内存占用大的问题。
全栈程序员站长
2022/07/02
3.2K0
Python数据分析(3)-numpy中nd数组的创建
摘要总结:本文主要介绍了NumPy(Numerical Python)中的一些数据结构和常用函数。主要包括:数组(array)、矩阵(matrix)、数组操作相关函数、矩阵操作相关函数、NumPy的子库numpy.core和numpy.lib等。通过这些函数,我们可以方便地进行数值计算、线性代数、傅里叶变换等操作,是进行科学计算和数据处理的重要工具。
锦小年
2018/01/02
2K0
Python数据分析(3)-numpy中nd数组的创建
点击加载更多

相似问题

PyQt:附标签的QLineEdit

20

DocuS传下载附标签的信封

10

将onclick添加到图像标签

24

将标签添加到图像链接

224

将图像添加到标签建议

23
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文