常见(但不常见)单子

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 分别用于将元
  • 树06--二叉树中和为某一值的路径

    树06 二叉树中和为某一值的路径 jz24 题目概述 解析 参考答案 注意事项 说明 题目概述 算法说明 输入一颗二叉树的根节点和一个整数 按字典序打印出二叉树中结点值的和为输入整数的所有路径 路径定义为从树的根结点开始往下一直到叶结点所经
  • 直线检测方法—LSD论文翻译

    附原文链接 LSD a Line Segment Detector 摘 要 LSD是一个线段检测器 能够在线性时间内得到亚像素级精度的检测结果 它无需调试参数就可以适用于任何数字图像上 并且能够自我控制错误数量的检测 平均来说 一个图像中允
  • 白盒测试相关的一些知识

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

    1 写出一个函数 取到链表中倒数第二个节点 双链表 node getSec List mylist return mylist m tail gt m prev m prev为链表前指针 单链表 node getSec List mylis
  • Hash table and application in java

    查找的效率取决于在查找是比较的次数 次数越少效率越高 反之越低 最理想的情况是无需比较 一次存取便能找到所查找的记录 根据对应关系f找到给定值K的像f K hash function 应运而生 由此思想建的表称为hash table 集合h
  • 数据结构----链式栈

    目录 前言 链式栈 操作方式 1 存储结构 2 初始化 3 创建节点 4 判断是否满栈 5 判断是否空栈 6 入栈 7 出栈 8 获取栈顶元素 9 遍历栈 10 清空栈 完整代码 前言 前面我们学习过了数组栈的相关方法 链接 线性表 栈 栈
  • findBug 错误修改指南

    FindBugs错误修改指南 1 EC UNRELATED TYPES Bug Call to equals comparing different types Pattern id EC UNRELATED TYPES type EC c
  • 『Python基础-15』递归函数 Recursion Function

    什么是递归函数 一种计算过程 如果其中每一步都要用到前一步或前几步的结果 称为递归的 用递归过程定义的函数 称为递归函数 例如连加 连乘及阶乘等 凡是递归的函数 都是可计算的 即能行的 递归就是一个函数在它的函数体内调用它自身 编程语言中的
  • 数据结构之图的两种遍历实现(C语言版)

    上一期文章分享完了图的两种遍历方式 也是两种很重要的算法 DFS和BFS 这两种算法的应用和重要性我就不多说了 内行的人懂的都懂 今天这文章重要就是来上机实现这两种算法 又由于这两种算法都可以由邻接矩阵和邻接表来表示 博主分享的代码都是上机
  • 数据结构与算法书籍推荐

    学习数据结构与算法 还是很有必要看几本相关的书籍 但根据不同基础的人 合适看的书也不一样 因此 针对不同层次 不同语言的人 推荐几本市面上口碑不错的书 1 入门级 针对刚入门的同学 建议不要急着去看那些经典书 像 算法导论 算法 这些比较经
  • 链表面试题(一):反转链表的算法实现

    关于链表的考察 链表是面试里面经常涉及到的考点 因为链表的结构相比于Hashmap Hashtable Concurrenthashmap或者图等数据结构简单许多 对于后者更多面试的侧重点在于其底层实现 比如Hashmap中Entry
  • 算法问题实战策略

    算法问题实战策略 基本信息作者 韩 具宗万 译者 崔盛一出版社 人民邮电出版社ISBN 9787115384621上架时间 2015 2 4出版日期 2015 年3月开本 16开页码 738版次 1 1 内容简介 算法问题实战策略 本书收录
  • CRC校验(二)

    CRC校验 二 参考 https blog csdn net liyuanbhu article details 7882789 https www cnblogs com esestt archive 2007 08 09 848856
  • 查找数组中第二大的数

    快速找出一个数组中的最大数 第二大数 思路 如果当 前元素大于最大数 max 则让第二大数等于原来的最大数 max 再把当前元素的值赋给 max 如果当前的元素大于等于第二大数secondMax的值而小于最大数max的值 则要把当前元素的值
  • 雪糕的最大数量 排序+贪心

    雪糕的最大数量 雪糕的最大数量 题目描述 样例 数据范围 思路 代码 题目描述 夏日炎炎 小男孩 Tony 想买一些雪糕消消暑 商店中新到 n 支雪糕 用长度为 n 的数组 costs 表示雪糕的定价 其中 costs i 表示第 i 支雪
  • 【数据结构入门精讲 | 第二篇】一文讲清算法复杂度

    上篇文章中我们引入了算法 数据结构 数据类型等概念 而要想衡量一个算法与数据结构是否为优质的 就需要一个衡量标准 这个衡量标准也是在我们实现一个好的算法时要遵循的原则 目录 基本概念 渐进性态 渐进性态数学表征 算法复杂度的运算 顺序搜索算
  • 【数据结构】双链表的定义和操作

    目录 1 双链表的定义 2 双链表的创建和初始化 3 双链表的插入节点操作 4 双链表的删除节点操作 5 双链表的查找节点操作 6 双链表的更新节点操作 7 完整代码 嗨 我是 Filotimo 很高兴与大家相识 希望我的博客能对你有所帮助
  • C++ AVL树(四种旋转,插入)

    C AVL树 四种旋转 插入 一 AVL树的概念及性质 二 我们要实现的大致框架 1 AVL树的节点定义 2 AVL树的大致框架 三 插入 1 插入逻辑跟BST相同的那一部分 2 修改平衡因子

随机推荐