当前位置: 首页 > 编程日记 > 正文

A monad tutorial for Clojure programmers (part 3)

Before moving on to the more advanced aspects of monads, let’s recapitulate what defines a monad (see part 1 and part 2 for explanations):

  1. A data structure that represents the result of a computation, or the computation itself. We haven’t seen an example of the latter case yet, but it will come soon.
  2. A function m-result that converts an arbitrary value to a monadic data structure equivalent to that value.
  3. A function m-bind that binds the result of a computation, represented by the monadic data structure, to a name (using a function of one argument) to make it available in the following computational step.

Taking the sequence monad as an example, the data structure is the sequence, representing the outcome of a non-deterministic computation, m-result is the function list, which converts any value into a list containing just that value, and m-bindis a function that executes the remaining steps once for each element in a sequence, and removes one level of nesting in the result.

The three ingredients above are what defines a monad, under the condition that the three monad laws are respected. Some monads have two additional definitions that make it possible to perform additional operations. These two definitions have the names m-zero and m-plusm-zero represents a special monadic value that corresponds to a computation with no result. One example is nil in the maybe monad, which typically represents a failure of some kind. Another example is the empty sequence in the sequence monad. The identity monad is an example of a monad that has no m-zero.

m-plus is a function that combines the results of two or more computations into a single one. For the sequence monad, it is the concatenation of several sequences. For the maybe monad, it is a function that returns the first of its arguments that is not nil.

There is a condition that has to be satisfied by the definitions of m-zero and m-plus for any monad:

(= (m-plus m-zero monadic-expression)(m-plus monadic-expression m-zero)monadic-expression)

In words, combining m-zero with any monadic expression must yield the same expression. You can easily verify that this is true for the two examples (maybe and sequence) given above.

One benefit of having an m-zero in a monad is the possibility to use conditions. In the first part, I promised to return to the :when clauses in Clojure’s for forms, and now the time has come to discuss them. A simple example is

(for [a (range 5):when (odd? a)](* 2 a))

The same construction is possible with domonad:

(domonad sequence[a (range 5):when (odd? a)](* 2 a))

Recall that domonad is a macro that translates a let-like syntax into a chain of calls to m-bind ending in a call to m-result. The clause a (range 5) becomes

(m-bind (range 5) (fn [a] remaining-steps))

where remaining-steps is the transformation of the rest of the domonad form. A :when clause is of course treated specially, it becomes

(if predicate remaining-steps m-zero)

Our small example thus expands to

(m-bind (range 5) (fn [a](if (odd? a) (m-result (* 2 a)) m-zero)))

Inserting the definitions of m-bindm-result, and m-zero, we finally get

(apply concat (map (fn [a](if (odd? a) (list (* 2 a)) (list))) (range 5)))

The result of map is a sequence of lists that have zero or one elements: zero for even values (the value of m-zero) and one for odd values (produced by m-result). concatmakes a single flat list out of this, which contains only the elements that satisfy the :when clause.

As for m-plus, it is in practice used mostly with the maybe and sequence monads, or with variations of them. A typical use would be a search algorithm (think of a parser, a regular expression search, a database query) that can succeed (with one or more results) or fail (no results). m-plus would then be used to pursue alternative searches and combine the results into one (sequence monad), or to continue searching until a result is found (maybe monad). Note that it is perfectly possible in principle to have a monad with an m-zero but no m-plus, though in all common cases an m-plus can be defined as well if an m-zero is known.

After this bit of theory, let’s get acquainted with more monads. In the beginning of this part, I mentioned that the data structure used in a monad does not always represent the result(s) of a computational step, but sometimes the computation itself. An example of such a monad is the state monad, whose data structure is a function.

The state monad’s purpose is to facilitate the implementation of stateful algorithms in a purely functional way. Stateful algorithms are algorithms that require updating some variables. They are of course very common in imperative languages, but not compatible with the basic principle of pure functional programs which should not have mutable data structures. One way to simulate state changes while remaining purely functional is to have a special data item (in Clojure that would typically be a map) that stores the current values of all mutable variables that the algorithm refers to. A function that in an imperative program would modify a variable now takes the current state as an additional input argument and returns an updated state along with its usual result. The changing state thus becomes explicit in the form of a data item that is passed from function to function as the algorithm’s execution progresses. The state monad is a way to hide the state-passing behind the scenes and write an algorithm in an imperative style that consults and modifies the state.

The state monad differs from the monads that we have seen before in that its data structure is a function. This is thus a case of a monad whose data structure represents not the result of a computation, but the computation itself. A state monad value is a function that takes a single argument, the current state of the computation, and returns a vector of length two containing the result of the computation and the updated state after the computation. In practice, these functions are typically closures, and what you use in your program code are functions that create these closures. Such state-monad-value-generating functions are the equivalent of statements in imperative languages. As you will see, the state monad allows you to compose such functions in a way that makes your code look perfectly imperative, even though it is still purely functional!

Let’s start with a simple but frequent situation: the state that your code deals with takes the form of a map. You may consider that map to be a namespace in an imperative languages, with each key defining a variable. Two basic operations are reading the value of a variable, and modifying that value. They are already provided in the Clojure monad library, but I will show them here anyway because they make nice examples.

First, we look at fetch-val, which retrieves the value of a variable:

(defn fetch-val [key](fn [s][(key s) s]))

Here we have a simple state-monad-value-generating function. It returns a function of a state variable s which, when executed, returns a vector of the return value and the new state. The return value is the value corresponding to the key in the map that is the state value. The new state is just the old one – a lookup should not change the state of course.

Next, let’s look at set-val, which modifies the value of a variable and returns the previous value:

(defn set-val [key val](fn [s](let [old-val (get s key)new-s   (assoc s key val)][old-val new-s])))

The pattern is the same again: set-val returns a function of state s that, when executed, returns the old value of the variable plus an updated state map in which the new value is the given one.

With these two ingredients, we can start composing statements. Let’s define a statement that copies the value of one variable into another one and returns the previous value of the modified variable:

(defn copy-val [from to](domonad state-m[from-val   (fetch-val from)old-to-val (set-val to from-val)]old-to-val))

What is the result of copy-val? A state-monad value, of course: a function of a state variable s that, when executed, returns the old value of variable to plus the state in which the copy has taken place. Let’s try it out:

(let [initial-state        {:a 1 :b 2}computation          (copy-val :b :a)[result final-state] (computation initial-state)]final-state)

We get {:a 2, :b 2}, as expected. But how does it work? To understand the state monad, we need to look at its definitions for m-result and m-bind, of course.

First, m-result, which does not contain any surprises: it returns a function of a state variable s that, when executed, returns the result value v and the unchanged state s:

(defn m-result [v] (fn [s] [v s]))

The definition of m-bind is more interesting:

(defn m-bind [mv f](fn [s](let [[v ss] (mv s)]((f v) ss))))

Obviously, it returns a function of a state variable s. When that function is executed, it first runs the computation described by mv (the first ‘statement’ in the chain set up by m-bind) by applying it to the state s. The return value is decomposed into result v and new state ss. The result of the first step, v, is injected into the rest of the computation by calling f on it (like for the other m-bind functions that we have seen). The result of that call is of course another state-monad value, and thus a function of a state variable. When we are inside our (fn [s] ...), we are already at the execution stage, so we have to call that function on the state ss, the one that resulted from the execution of the first computational step.

The state monad is one of the most basic monads, of which many variants are in use. Usually such a variant adds something to m-bind that is specific to the kind of state being handled. An example is the the stream monad in clojure.contrib.stream-utils. (NOTE: the stream monad has not been migrated to the new Clojure contrib library set.) Its state describes a stream of data items, and the m-bind function checks for invalid values and for the end-of-stream condition in addition to what the basicm-bind of the state monad does.

A variant of the state monad that is so frequently used that has itself become one of the standard monads is the writer monad. Its state is an accumulator (any type implementing th e protocol writer-monad-protocol, for example strings, lists, vectors, and sets), to which computations can add something by calling the function write. The name comes from a particularly popular application: logging. Take a basic computation in the identity monad, for example (remember that the identity monad is just Clojure’s built-in let). Now assume you want to add a protocol of the computation in the form of a list or a string that accumulates information about the progress of the computation. Just change the identity monad to the writer monad, and add calls to write where required!

Here is a concrete example: the well-known Fibonacci function in its most straightforward (and most inefficient) implementation:

(defn fib [n](if (< n 2)n(let [n1 (dec n)n2 (dec n1)](+ (fib n1) (fib n2)))))

Let’s add some protocol of the computation in order to see which calls are made to arrive at the final result. First, we rewrite the above example a bit to make every computational step explicit:

(defn fib [n](if (< n 2)n(let [n1 (dec n)n2 (dec n1)f1 (fib n1)f2 (fib n2)](+ f1 f2))))

Second, we replace let by domonad and choose the writer monad with a vector accumulator:

(with-monad (writer-m [])(defn fib-trace [n](if (< n 2)(m-result n)(domonad[n1 (m-result (dec n))n2 (m-result (dec n1))f1 (fib-trace n1)_  (write [n1 f1])f2 (fib-trace n2)_  (write [n2 f2])](+ f1 f2)))))

Finally, we run fib-trace and look at the result:

(fib-trace 3)
[2 [[1 1] [0 0] [2 1] [1 1]]]

The first element of the return value, 2, is the result of the function fib. The second element is the protocol vector containing the arguments and results of the recursive calls.

Note that it is sufficient to comment out the lines with the calls to write and change the monad to identity-m to obtain a standard fib function with no protocol – try it out for yourself!

Part 4 will show you how to define your own monads by combining monad building blocks called monad transformers. As an illustration, I will explain the probability monad and how it can be used for Bayesian estimates when combined with the maybe-transformer.

转载于:https://www.cnblogs.com/javamoon/p/4112398.html

相关文章:

Flex精华摘要--使用AS脚本

在MXML文件中实现ActionScript逻辑的几种方法&#xff1a;最简单的方法&#xff0c;在一个MXML文件中通过组件的事件直接书写简单的逻辑控制&#xff0c;但是并不推荐。 <?xml version"1.0" encoding"utf-8"?> <mx:Application xmlns:mx"h…

(C++)自定义链表并写入

确定链表节点的组成&#xff0c;一般由数据和指针构成 struct node{int data;//数据域node* next;//指针域 }; 使用new运算符为节点分配内存空间 node* p new node; 编写创建列表函数&#xff0c;参数为链表的长度(从用户输入读入)&#xff0c;返回值为创建的列表的头指针…

Unicode转义(\uXXXX)的编码和解码

在涉及Web前端开发时, 有时会遇到\uXXXX格式表示的字符, 其中XXXX是16进制数字的字符串表示形式, 在js中这个叫Unicode转义字符, 和\n \r同属于转义字符. 在其他语言中也有类似的, 可能还有其它变形的格式. 多数时候遇到需要解码的情况多点, 所以会先介绍解码decode, 后介绍…

BZOJ 2004 [Hnoi2010]Bus 公交线路

题目链接 https://www.lydsy.com/JudgeOnline/problem.php?id2004 题解 状压dp&#xff0c;记f[i][S]f[i][S]f[i][S]表示[1,i−p][1,i-p][1,i−p]的车都被安排好了&#xff0c;而[i−p1,i][i-p1,i][i−p1,i]的车中&#xff0c;SSS中有111的位置都安排有车停&#xff0c;并且恰…

【转载】C语言编译全过程

今天在blog.chinaunix.net/u3博客看到一篇关于语言编译过程的文章&#xff0c;觉得精简&#xff0c;清晰所以摘录下来我的blog。作为一个程序员了解编译过程对程序的编写也很有帮助。下面是博文的内容&#xff1a;编译的概念&#xff1a;编译程序读取源程序&#xff08;字符流&…

5层模型中数据从源主机到目的主机之旅

报文是用户发送的数据 传输层可能对报文进行拆分&#xff0c;加上段头 网络层会加上网络层的头&#xff0c;构成的协议数据单元叫做数据报 链路层会加头加尾构造帧 路由器的链路层会去掉帧头帧尾&#xff0c;还原到网络层数据报 再次封装成链路层的数据帧 目的主机的链路层再…

JavaScript模式读书笔记 第5章 对象创建模式

1&#xff0c;命名空间模式 namespace <script>var myApp {};//通过全局变量来实现命名空间maApp.Parent function (){};myApp.Child function(){};</script>通用命名空间函数<script>//不安全代码var myApp {};//安全代码if(typeof myApp "undef…

174. Dungeon Game

一、题目 1、审题 2、分析 只能向右、向下移动的王子&#xff0c;从左上角要到右下角救公主&#xff0c;每经过一个方格&#xff0c;可能获得血瓶加血量&#xff0c;或者碰到怪物减血量&#xff0c;当王子血量 < 1 时就挂了&#xff0c;为了能成功救得公主&#xff0c;求王子…

DotNetNuke安装与下载

【下载专区】 DotNetNuke (DNN) 5.1 稳定版正式发布 http://www.dnnmix.com/dotnetnuke-dnn-51-released/ DotNetNuke (DNN) 资源共享 http://www.dnnmix.com/resources/ DotNetNuke官方下载 http://www.dotnetnuke.com/tabid/125/default.aspx 【安装教程】 DotNetNuke安装大…

1025 反转链表

1. 第一次做链表题&#xff0c;但是这题其实也就是套了个链表的壳子&#xff0c;虽然在结点的结构体里面有下一节点地址next这个属性&#xff0c;但是也只在最初给结点标序号时用到&#xff0c;由于没有真正对链表实施倒序&#xff0c;所以后面输出的下一结点的地址实际上只是算…

关于margin

<html><body><div style"width:200px;height:200px;background-color:red;> <div style"width:100px;height:100px;background-color:black;margin-left:300px;"></div></div></body></html> 左边是火狐显示&a…

DNS迭代式和递归式域名查询对比

背景知识&#xff1a;DNS数据库是树状的层次式的 本地域名服务器并不在这个体系当中&#xff0c;它相当于这个体系面向用户的代理。 迭代式&#xff1a;DNS server告诉用户&#xff1a;我不认识这域名&#xff0c;但我知道你可以问哪个DNS服务器 递归式&#xff1a;用户告诉D…

UIActionSheet在iOS8中被弃用造成的错误

UIActionSheet在iOS7.0中效果图如下&#xff1a; UIActionSheet在iOS8中效果图如下&#xff1a; 造成这样的原因&#xff0c;是因为此控件在iOS8中被弃用了&#xff0c;而使用了UIAlertViewController替代的原因&#xff0c;具…

SQL分页语句(转)

有关分页 SQL 的资料很多&#xff0c;有的使用存储过程&#xff0c;有的使用游标。本人不喜欢使用游标&#xff0c;我觉得它耗资、效率低&#xff1b;使用存储过程是个不错的选择&#xff0c;因为存储过程是经过预编译的&#xff0c;执行效率高&#xff0c;也更灵活。先看看单条…

一个考查作用域以及闭包的题目

var a 2;var func (function(){ var a 3; return function(){a;console.log(a); } })(); func();func(); 1.涉及的知识点&#xff1a; &#xff08;1&#xff09;JS变量的作用域 &#xff08;2&#xff09;闭包2.变量的作用域&#xff0c;通俗来说就是变量所能起到作用的范围…

弄懂“进程”(上):3个组成部分、4个基本特征、4个基本状态

目录 进程实体的三个部分 1.PCB 2.程序段 3.相关的数据段 进程的四大特征 1.动态性 2.并发性 3.独立性 4.异步性 进程的状态(3个基本挂起) 1.三个基本状态 2.挂起状态 进程实体的三个部分 1.PCB 作用是让参与并发执行的每个程序独立运行&#xff0c;或者说&…

解决Failed to execute goal org.apache.maven.plugins

1.Maven构建失败 Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin: 2.3 . 2 :compile ( default-compile) on project oecp: Compilation failure 2.解决方法 把jdk换成自己安装的jdk 换后&#xff0c;再maven install就可以了转载于:https://www.cnb…

P4722 【模板】最大流

P4722 【模板】最大流 加强版 / 预流推进 今日心血来潮&#xff0c;打算学习hlpp 然后学了一阵子。发现反向边建错了。容量并不是0.qwq 然后就荒废了一晚上。 算法流程的话。有时间补上 #include<cstdio> #include<algorithm> #include<iostream> #include&l…

与我们的书合影——在2009北京国际图书展(BIBF)

2009年9月5日&#xff0c;武汉博文编辑许莹、夏青观看了于国展旧馆&#xff08;静安庄&#xff09;举行的2009北京国际图书展&#xff08;BIBF&#xff09;“专业场”。在电子工业出版社展台&#xff0c;编辑兴奋地与我们的几本畅销书&#xff08;《把时间当作朋友》、《走出软…

弄懂“进程”(下):进程的控制、同步和通信

进程控制 是进程管理的主要功能&#xff0c;负责创建和终止进程、进程执行过程中的状态转换。 由操作系统内核通过原语实现。 1.OS内核 常驻内存的、紧靠硬件的软件层次&#xff0c;运行在系统态(又称管态、内核态)&#xff0c;以免遭到用户程序的破坏。 主要包括&#xf…

(转自Timon's wang blogs)C#实现web信息自动抓取

原文转自&#xff1a;http://www.csharp.net.cn/post/C实现web信息自动抓取.html主要为了学习一下相关的网络蜘蛛&#xff0c;为自己获取信息使用背景 随着Internet的普及&#xff0c;网络信息正以极高的速度增长&#xff0c;在这么多数据中找到自己需要的信息是一件很繁琐的事…

数据结构-栈与队列

栈的定义 栈是限定仅在表尾进行插入和删除操作的线性表 我们把允许插入和删除的一端称为栈顶 (top) &#xff0c;另一端称为栈底 (bottom) &#xff0c;不含任何数据元素的栈称为空栈。 栈又称为后进先出 (Last In Filrst Out) 的线性表&#xff0c;简称LIFO结构。 理解栈的定义…

雷观(七):靠谱的程序员,不是随便一个码农就可以做到的

在学习Web开发4年之后&#xff0c;我自己可以独立做一些基本的项目了。在加入前单位秒针&#xff0c;也做了几个Web项目。 我发现一个现象&#xff0c;很多公司大部分的Web项目&#xff0c;用到的技术很少&#xff0c;主要就是SSH等框架&#xff0c;实现一些行业的业务逻辑&…

1032 Sharing

思路很简单&#xff0c;不要想多了&#xff0c;就是找第一个出现的共同字母&#xff0c;即使后面不一样了也没关系。 经验收获如下&#xff1a; 1. 5位整数算小整数&#xff0c;用静态链表&#xff0c;本质上是哈希。 2. 读入字符型的时候千万注意空格。 AC代码 #include&…

Magento开发的特点有哪些?

Magento是一套专业开源的电子商务系统&#xff0c;也是目前主流的外贸网站购物系统&#xff0c;都是居于PHP语言开发的&#xff0c;数据库使用的是Mysql&#xff0c;且浏览界面很适合欧美用户的使用习惯。Magento开发设计得非常灵活&#xff0c;具有模块化架构体系和丰富的功能…

Linux多任务编程之五:exit()和_exit()函数(转)

来源&#xff1a;CSDN 作者&#xff1a;王文松 转自&#xff1a;Linux公社 ------------------------------------------------------------------------------------------------------------------------------------------------ wait()和waitpid() 函数说明 wait()函数用…

LogMiner日志分析工具的使用

1.安装logminer&#xff1a; 要安装LogMiner工具&#xff0c;必须首先要运行下面这样两个脚本&#xff0c; $ORACLE_HOME/rdbms/admin/dbmslm.sql $ORACLE_HOME/rdbms/admin/dbmslmd.sql. 这两个脚本必须均以SYS用户身份运行。 *************使用字典文件…

1052 Linked List Sorting

1. 开始测试点4不通过&#xff0c;得分24/25&#xff0c;是忽略了所有节点都不在链表上的特殊情况。 2. 其实就是用静态链表&#xff0c;把结点根据值的大小&#xff0c;升序排列&#xff0c;所以一开始把每个结点的key赋值为超出最大值的maxn&#xff0c;是为了方便输出。 3…

C#尝试读取或写入受保护的内存。这通常指示其他内存已损坏。

用VS2012调试时发现在调用数据集时提示“尝试读取或写入受保护的内存。这通常指示其他内存已损坏。” 用管理员身份运行CMD&#xff0c;输入netsh winsock reset并回车 转载于:https://www.cnblogs.com/CandiceW/p/4204552.html

TFS2008 + Windows2003 + Sql2005 安装注意事项

TFS2008并不是一个很容易安装的软件&#xff0c;很多时候能否顺利安装成功跟人品有关(笑)&#xff0c;要想一次安装成功&#xff0c;强烈建议准备一个全新的干净系统。 1.系统 最好采用刚安装好的windows2003&#xff0c;注意要打上sp2&#xff0c;安装IIS(如果IIS默认站点的主…