常见(但不常见)单子

2023-11-17

上周,我们研究了monad如何帮助您实现Haskell开发的下一个跃进。 我们讨论了runXXXT模式,以及如何使用其余代码中的某些monad作为通用网关。 但是有时它也有助于回到基础知识。 实际上,我花了很长时间才真正掌握如何使用几个基本的monad。 或者至少,我不了解如何将它们用作单子。

在本文中,我们将研究如何使用列表monad和函数monad。 列表和功能是任何Haskeller从一开始就学到的核心概念。 但是列表数据结构和功能应用程序也是单子! 理解它们的工作方式可以使我们更多地了解单子的工作原理。

有关monad的深入讨论,请查看我们的功能数据结构系列

执行语法的一般模式

使用do语法是了解如何实际使用monad的关键之一。 绑定运算符使您很难跟踪参数的位置。 Do语法可使结构保持整洁,并允许您轻松传递结果。 让我们看看它如何与IO ,这是许多Haskellers学习的第一个monad。 这是一个示例,其中我们从文件中读取第二行:

readLineFromFile :: IO String
readLineFromFile = do
handle <- openFile “myFile.txt” ReadMode
nextLine <- hGetLine handle
secondLine <- hGetLine handle
_ <- hClose handle
return secondLine

通过牢记所有IO函数的类型签名,我们可以开始了解do语法的一般模式。 让我们将每个表达式替换为其类型:

openFile :: FilePath -> IOMode -> IO Handle
hGetLine :: Handle -> IO String
hClose :: Handle -> IO ()
return :: a -> IO a
readLineFromFile :: IO String
readLineFromFile = do
(Handle) <- (IO Handle)
(String) <- (IO String)
(String) <- (IO String)
() <- (IO ())
IO String

do表达式中的每一行(最后一行除外)都使用赋值运算符<- 。 然后,它具有的表达IO a在右侧,这将其分配给的值a在左侧。 然后,最后一行的类型与该函数的最终返回值匹配。 现在重要的是要认识到我们可以将这种结构推广到任何monad:

monadicFunction :: mc
monadicFunction = do
(_ :: a) <- (_ :: ma)
(_ :: b) <- (_ :: mb)
(_ :: mc)

因此,例如,如果我们在Maybe monad中有一个函数,则可以使用它并将其插入上面的m

myMaybeFunction :: a -> Maybe a
monadicMaybe :: a -> Maybe a
monadicMaybe x = do
(y :: a) <- myMaybeFunction x
(z :: a) <- myMaybeFunction y
(Just z :: Maybe a)

要记住的重要一点是,monad会捕获计算上下文。 对于IO ,上下文是计算可能与终端或网络交互。 对于Maybe ,上下文是计算可能会失败。

列表单子

现在要绘制列表单子图,我们需要知道其计算上下文。 我们可以将任何返回列表的函数视为不确定的 。 它可以具有许多不同的值。 因此,如果我们链接这些计算,则最终结果就是每个可能的组合 。 也就是说,我们的第一个计算可以返回值列表。 然后,我们想检查一下这些不同结果所得到的结果,作为对下一个函数的输入。 然后,我们将获得所有这些结果。 等等。

看到这个,让我们想象一下我们有一个游戏。 我们可以从特定数字x开始游戏。 在每一回合中,我们可以减去1,加1或保持数字相同。 我们想知道5转后的所有可能结果以及这些可能性的分布。 因此,我们首先编写非确定性函数。 它需要一个输入并返回可能的游戏输出:

runTurn :: Int -> [Int]
runTurn x = [x - 1, x, x + 1]

这就是我们在这5回合游戏中的应用方式。 我们将添加类型签名,以便您可以看到单子结构:

runGame :: Int -> [Int]
runGame x = do
(m1 :: Int) <- (runTurn x :: [Int])
(m2 :: Int) <- (runTurn m1 :: [Int])
(m3 :: Int) <- (runTurn m2 :: [Int])
(m4 :: Int) <- (runTurn m3 :: [Int])
(m5 :: Int) <- (runTurn m4 :: [Int])
return m5

在右侧,每个表达式都具有[Int]类型。 然后在左侧,我们将Int输出。 因此, m表达式中的每一个表示我们将从runTurn获得的众多解决方案runTurn 。 然后,我们运行其余功能,假设我们仅使用其中之一。 但实际上,由于list monad如何定义其绑定运算符,我们将全部运行它们。 这种精神上的跳跃有些棘手。 而且,当我们进行列表计算时,只坚持使用where表达式通常更直观。 但是看到这样的模式突然出现在意外的地方很酷。

功能单子

函数monad是我一段时间以来一直难以理解的另一个函数。 在某些方面,它与Reader monad相同。 它封装了可以传递给不同函数的单个参数的上下文。 但这与Reader定义方式不同。 当我尝试使用该定义时,对我而言并没有多大意义:

instance Monad ((->) r) where
return x = \_ -> x
h >>= f = \w -> f (hw) w

return定义是有意义的。 我们将有一个函数,该函数接受一些参数,忽略该参数,并将值作为输出。 绑定运算符稍微复杂一点。 当我们将两个函数绑定在一起时,我们将获得一个带有一些参数w的新函数。 我们将该参数应用于第一个函数( (hw) )。 然后,我们将得出结果,并将其应用于f ,然后再再次应用参数w 。 很难遵循。

但是让我们在do语法的上下文中考虑一下。 右侧的每个表达式都是一个将我们的类型作为唯一参数的函数。

myFunctionMonad :: a -> (x, y, z)
myFunctionMonad = do
x <- :: a -> b
y <- :: a -> c
z <- :: a -> d
return (x, y, z)

现在让我们想象一下,我们将传递一个Int并使用一些可以接受Int不同函数。 这是我们将得到的:

myFunctionMonad :: Int -> (Int, Int, String)
myFunctionMonad = do
x <- (1 +)
y <- (2 *)
z <- show
return (x, y, z)

现在我们有了有效的do语法! 那么当我们运行此功能时会发生什么呢? 我们将在同一输入上调用不同的函数。

>> myFunctionMonad 3
(4, 6, "3")
>> myFunctionMonad (-1)
(0, -2, "-1")

当我们在第一个例子中通过3中,我们在第二行上加1它在第一行上,乘以它是2, show它在第三行上。 而且我们在没有明确说明参数的情况下完成了所有这些工作! 棘手的是,所有函数都必须将输入参数作为最后一个参数。 因此,您可能需要进行一些参数翻转。

结论

在本文中,我们探讨了列表和函数,这是Haskell中最常见的两个概念。 我们通常不将它们用作单子。 但是我们看到了它们仍然如何适应单子结构。 我们可以在do语法中使用它们,并遵循我们已经知道的模式使事情起作用。

也许您曾经尝试过学习Haskell,但是发现monad有点太复杂了。 希望本文有助于阐明monad的结构。 如果您想让自己的Haskell旅程重新开始,请下载我们的初学者清单 ! 或者从头开始学习monad,请阅读我们有关功能数据结构的系列!

From: https://hackernoon.com/common-but-not-so-common-monads-ae7ded7911d2

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

常见(但不常见)单子 的相关文章

  • C语言/C++实现栈操作

    一 栈的概念 栈是一种常用的数据结构 它遵循先入后出 Last In First Out LIFO 的原则 栈的操作只在栈的一端进行 该端被称为栈顶 而另一端称为栈底 栈的基本操作包括压栈 入栈 push 和弹栈 出栈 pop 分别用于将元
  • 以OpenGL/ES视角介绍gfx-hal(Vulkan) Shader/Program接口使用

    文档列表见 Rust 移动端跨平台复杂图形渲染项目开发系列总结 目录 背景 The right way to tackle this in Vulkan is to use resource descriptors A descriptor
  • 01背包问题变种:从长度为n的数组里选出m个数使和为固定值sum

    这个问题是我从leetcode上一道问题所想到的 原题 如果是从数组中选出2个数相加使之成为固定的数sum 这当然很简单 把数组中的数字遍历一遍 判断另一个数字是否也在数组中即可 代码如下 vector
  • 数据库多维迭代算法

    关键词 数据库 迭代 递归 多维 一 两种传统的数据库迭代结构算法 对于数据库的迭代结构 有两种传统的算法 递归算法和边界算法 比如对于下面图1的结构 图1 递归算法的数据结构如表1所示 节点id 节点值 父节点id 1 1111 2 3
  • 白盒测试相关的一些知识

    在白盒测试中 可以使用各种测试方法进行测试 下面这篇文章 可能比较枯燥 如果不乐意读 可以先收藏 如果在你的工作中真遇到白盒测试的话 可以回过头再来看看 还是值得读一读 一般来说 白盒测试时要考虑以下5个问题 1 测试中尽量先用自动化工具来
  • 一文弄懂循环链表、双向链表、静态链表

    循环链表 双向链表 静态链表 三遍定律 理解了单链表本文的理解易如反掌 单链表请点击这里 理解了单链表本文的理解易如反掌 单链表请点击这里 理解了单链表本文的理解易如反掌 单链表请点击这里 1 循环链表 将单链表中终端结点的指针端由空指针改
  • findBug 错误修改指南

    FindBugs错误修改指南 1 EC UNRELATED TYPES Bug Call to equals comparing different types Pattern id EC UNRELATED TYPES type EC c
  • 用 Java 实现的八种常用排序算法

    八种排序算法可以按照如图分类 前置知识 1 算法稳定性 在一个序列中 能保证两个相等的数 经过排序之后 其在序列的前后位置顺序不变 A1 A2 排序前 A1 在 A2 前面 排序后 A1 还在 A2 前面 2 时间复杂度 时间复杂度是用于衡
  • SDUT--OJ《数据结构与算法》实践能力专题训练6 图论

    A 数据结构实验之图论一 基于邻接矩阵的广度优先搜索遍历 Description 给定一个无向连通图 顶点编号从0到n 1 用广度优先搜索 BFS 遍历 输出从某个顶点出发的遍历序列 同一个结点的同层邻接点 节点编号小的优先遍历 Input
  • 数据结构小白之插入排序算法

    1 插入排序 1 1 思路 将n个需要排序的元素看成两个部分 一个是有序部分 一个是无序部分 开始的时候有序表只有一个元素 无序表有n 1个元素 排序过程中每次从无序表中取出元素 然后插入到有序表的适当位置 从而成为新的有序表 类似排队 如
  • 算法系列15天速成——第八天 线性表【下】

    一 线性表的简单回顾 上一篇跟大家聊过 线性表 顺序存储 通过实验 大家也知道 如果我每次向 顺序表的头部插入元素 都会引起痉挛 效率比较低下 第二点我们用顺序存储时 容 易受到长度的限制 反之就会造成空间资源的浪费 二 链表 对于顺序表存
  • 字符串09--表示数值的字符串

    字符串09 表示数值的字符串 jz53 题目概述 解析 参考答案 注意事项 说明 题目概述 算法说明 请实现一个函数用来判断字符串是否表示数值 包括整数和小数 例如 字符串 100 5e2 123 3 1416 和 1E 16 都表示数值
  • 4Sum

    Given an array S of n integers are there elements a b c and d in S such that a b c d target Find all unique quadruplets
  • 用指针访问一维数组

    文章目录 顺序查找 数组方式实现 指针实现方式 对一位数组元素的访问有三种方式 指针变量的关系运算 引例 数组实现方式 主函数 指针实现方式 主函数 一维数组作为函数的参数 实际应用 顺序查找 要求用指针实现 在整数集合r中顺序查找与给定值
  • 数理统计知识整理——回归分析与方差分析

    题记 时值我的北科研究生第一年下 选学 统计优化 课程 备考促学 成此笔记 以谨记 1 线性回归 1 1 原理分析 要研究最大积雪深度x与灌溉面积y之间的关系 测试得到近10年的数据如下表 使用线性回归的方法可以估计x与y之间的线性关系 线
  • 【试题】排列组合

    在写一个远程的代码 如果本地有M个显示器 远程有N个显示器 M lt N 依据分辨率 显示器刷新频率等要求 需要对远程的N个显示器进行最佳分辨率修改 之后 需要从N个远程显示器中选择M个 跟本地显示器进行一对一的匹配 即从 A N M N
  • 插入排序超详解释,一看就懂

    目录 一 插入排序的相关概念 1 基本思想 2 基本操作 有序插入 二 插入排序的种类 三 直接插入排序 1 直接插入排序的过程 顺序查找法查找插入位置 2 使用 哨兵 直接插入排序 四 直接插入排序算法描述 五 折半插入排序 1 查找插入
  • 基数排序代码实现

    详情请看排序总结 传送门 https blog csdn net m0 52711790 article details 121914543 基数排序的知识点我就不贴出来 相信都能搜到对应概念解释 下面就直接上代码 代码解释其实也很清晰了
  • 浅谈归并排序:合并 K 个升序链表的归并解法

    在面试中遇到了这道题 如何实现多个升序链表的合并 这是 LeetCode 上的一道原题 题目具体如下 用归并实现合并 K 个升序链表 LeetCode 23 合并K个升序链表 给你一个链表数组 每个链表都已经按升序排列 请你将所有链表合并到
  • 排序:计数排序

    一 概念 计数排序是非比较排序 是对哈希直接定址法的变形应用 二 思想 利用数组统计相同数据出现的次数 例如整型数据m出现n次 就在数组m位置记录数据为n 最后从头遍历数组打印数据即可 通俗来讲就是 数组下标即为数据 下标所指位置的值即为数

随机推荐