haskell - 如何从数学角度看待高阶函数和 IO 动作?
问题描述
我试图从第一原理理解函数式编程,但我被困在纯函数世界和具有状态和副作用的不纯现实世界之间的接口上。从数学的角度来看,
- 什么是返回函数的函数?
- 什么是返回 IO 动作的函数(如 Haskell 的 IO 类型)?
详细说明:在我的理解中,纯函数是从域到共域的映射。最终,它是从计算机内存中的某些值到内存中的某些其他值的映射。在函数式语言中,函数是声明式定义的;即,它们描述了映射,而不是需要对特定输入值执行的实际计算;后者由编译器派生。在具有备用内存的简化设置中,运行时不会进行计算;相反,编译器可以在编译时为每个函数创建一个查找表。执行纯程序相当于查表。因此,组合函数相当于构建更高维的查找表。当然,拥有计算机的全部意义在于设计方法来指定函数,而无需逐点查找表——但我发现心智模型有助于区分纯函数和效果。但是,我很难将这种心智模型用于高阶函数:
- 对于将另一个函数作为参数的函数,将值映射到值的结果一阶函数是什么?是否有它的数学描述(我确定有,但我既不是数学家也不是计算机科学家)。
- 返回函数的函数怎么样?我怎样才能在精神上“扁平化”这个构造以再次获得将值映射到值的一阶函数?
现在进入令人讨厌的现实世界。与它的交互并不纯粹,但没有它,就没有明智的程序。在我上面的简化心智模型中,分离程序的纯部分和不纯部分意味着每个函数式程序的基础是一层命令式语句,这些语句从现实世界中获取数据,对其应用纯函数(进行表查找),以及然后将结果写回现实世界(磁盘、屏幕、网络等)。
在 Haskell 中,这种与现实世界的命令式交互被抽象为IO 动作,编译器根据它们的数据依赖性对其进行排序。但是,我们不会将程序直接编写为命令式 IO 操作的序列。相反,有些函数会返回 IO 操作(类型为 的函数:: IO a
)。但据我了解,这些不可能是真正的功能。这些是什么?如何根据上述心智模型最好地思考它们?
解决方案
从数学上讲,接受或返回其他函数的函数完全没有问题。从集合S到集合T的函数的标准集合论定义是:
f ∈ S → T意味着f ⊂ S ✕ T和两个条件成立:
- 如果s ∈ S,那么(s, t) ∈ f对于某个t,并且
- 如果(s, t) ∈ f和(s, t') ∈ f,则t = t'。
我们将f(s) = t写成(s, t) ∈ f 的一种方便的符号简写。
所以写S → T只是表示一个特定的集合,因此(A → B) → C和A → (B → C)再次只是特定的集合。
当然,为了提高效率,我们不会将内存中的函数内部表示为这样的输入-输出对集合,但这是一个不错的第一个近似值,如果您想要数学直觉,可以使用它。(第二个近似值需要做更多的工作才能正确设置,因为它使用您可能还没有体验过的结构以谨慎、有原则的方式处理惰性和递归。)
IO 操作有点棘手。您如何看待它们可能在某种程度上取决于您特定的数学倾向。
一位数学家可能希望将 IO 动作定义为归纳集,例如:
- 如果
x :: a
,那么pure x :: IO a
。 - 如果
f :: a -> b
,那么fmap f :: IO a -> IO b
。 - 如果
x :: IO a
和f :: a -> IO b
,那么x >>= f :: IO b
。 putStrLn :: String -> IO ()
forkIO :: IO a -> IO ThreadId
- ...以及一千个其他基本案例。
- 我们对几个等式进行商:
fmap id = id
fmap f . fmap g = fmap (f . g)
pure x >>= f
=f x
x >>= pure . f
=fmap f x
- (还有一个读起来有点复杂的,只是说它
>>=
是联想的)
就定义程序的含义而言,这足以指定 IO 系列类型可以容纳的“值”。您可能会从定义自然数的标准方式中认出这种定义方式:
- 零是一个自然数。
- 如果n是自然数,则Succ(n)是自然数。
当然,这种定义事物的方式有一些事情不是很令人满意。比如:任何特定的 IO 操作是什么意思?这个定义中没有任何内容说明这一点。(尽管请参阅“处理尴尬的小队”以阐明即使您采用这种类型的归纳定义,您如何说出 IO 操作的含义。)
另一种数学家可能更喜欢这种定义:
一个 IO 动作同构于代表宇宙当前状态的幻象标记上的有状态函数:
IO a ~= RealWorld -> (RealWorld, a)
这种定义也很有吸引力。但是,值得注意的是,要说出这种定义到底forkIO
做了什么变得更加困难。
...或者您可以采用 GHC 定义,在这种情况下,如果您足够深入地了解,则 anIO a
是秘密的。a
但是,嘘!!,不要告诉没有经验的程序员,因为他们还不了解如何使用接口进行编程,所以他们只是想逃避IO
并编写一个函数!IO a -> a
IO
推荐阅读
- android - 查看绑定错误:Member Injector 被多次定义
- sql - 将字符串日期转换为 Unix 时间戳 Sqlite3
- php - 缺货采购高于库存
- c - 与等效文本文件相比,将 C 代码中的数据保存在较小的文件中
- python - 由于 scipy,sklearn 导入失败。OSError: [WinError 126] 找不到指定的模块
- keycloak - Keycloak SPI:getUserByUsername 中的访问密码
- python - 从存储在字符串变量中的日期获取上个月的日期
- svg - 字体真棒 SVG 图标在 safari (iOS) 上不起作用
- javascript - DOM- XPath - 通过属性值 StartsWith 选择节点
- python - 使用阈值创建张量