Clojure programmer's monad journey (Part 2)

Source: Internet
Author: User

Translated from a monad tutorial for clojure programmers (Part 2)

In Part1, we have learned the two most basic monad: identity monad and maybe monad. In this section, we will continue to introduce sequence monad and contact the M-result function for explanation. Finally, I will demonstrate two useful monad wildcard operators.

Sequence monad (which corresponds to list monad in Haskell) is one of the most frequently used monads. Clojure also has this monad, such as. Let's take a look at the following example:

1 (for [A (range 5)
2 B (range a)]
3 (* a B ))

 

The syntax of for and let is similar. They have the same structure: a list composed of binding expressions. Each binding expression can use the symbols of the previous expression. A Result Expression, this expression usually requires the previous binding. The difference is that let binds a single value to each symbol, and for binds a sequence. For must be bound to a sequence, and the returned result is also a sequence. For can be used with conditional expressions: When and: While. From the monad composite operation point of view, the sequence operation result can be considered non-single, for example, the operation result is more than one situation.

Using the monad library, the above loop can be written:

1 (domonad sequence-m
2 [A (range 5)
3 B (range a)]
4 (* a B ))

 

We already know that the domonad macro is a m-bind operation chain and calls the M-result function at the end. Next we will explain how to define M-bind and M-result to achieve the cyclic effect.

As we can see above, M-bind calls a function that represents the remaining calculation steps. The parameter is the bound value. To achieve the loop effect, we need to call this function again. The first step is to construct a function like this:

1 (defn M-bind-first-try [sequence function]
2 (map function sequence ))
3
4 (M-bind-first-Try (range 5) (FN [A]
5 (m-bind-first-Try (range A) (FN [B]
6 (* a B )))))

 

Result: () (0) (0 2) (0 3 6) (0 4 8 12 )), the result of the for expression is (0 0 2 0 3 6 0 4 8 12 ). We want a sequence without nesting, because the number of nested layers is the same as the number of calls to m-bind. Since m-bind introduces a nesting, we need to find a way to remove this nesting. This seems to be solved with Concat, so let's try again:

1 (defn M-bind-second-try [sequence function]
2 (apply Concat (map function sequence )))
3
4 (M-bind-second-Try (range 5) (FN [A]
5 (m-bind-second-Try (range A) (FN [B]
6 (* a B )))))

 

Worse this time, we got an exception.

Java. Lang. illegalargumentexception: Don't Know How To create iseq from: integer

Let's think about it! Each m-bind introduces a layer of nesting, while eliminating one nesting. The number of nested layers of the called function determines the number of nested layers of the result. The number of nested layers in our final result is the same as (* a B), that is, no nesting. If we want to implement layer-1 nesting in the results, it has nothing to do with the number of M-bind calls. The correct method is to introduce nesting in the last calculation:

1 (m-bind-second-Try (range 5) (FN [A]
2 (M-bind-second-Try (range A) (FN [B]
3 (List (* a B ))))))

 

Everything works. Our (FN [B]...) always returns a list of single elements. The M-bind in the inner layer creates a sequence of a single element. Each element is a value of B, and these values form a list without nesting. The M-bind of the outer layer creates a list composed of the values of. The result of each M-bind is also a non-nested list. This demonstrates the role of M-result in monad. The final definition of sequence monad is as follows:

1 (defn M-bind [sequence function]
2 (apply Concat (map function sequence )))
3
4 (defn M-result [value]
5 (List Value ))

 

M-result is used to return a value and bind the symbol to the value when it appears on the right of the monad binding. When defining monad, M-bind and M-result must meet this condition. The clojure code is shown as follows:

(= (M-bind (m-result value) function)

(Function value ))

There are two other monad rules, one of which is:

(= (M-bind monadic-expression m-result)

Monadic-expression)

 

Monadic-expression represents any valid monad expression, such as a sequence monad expression. You can use the domonad macro to better understand this rule.

(= (Domonad

[X monadic-expression]

X)

Monadic-expression)

The last rule is

(= (M-bind monadic-expression

Function1)

Function2)

(M-bind monadic-expression

(FN [x] (M-bind (function1 X)

Function2 ))))

Use domonad

(= (Domonad

[Y (domonad

[X monadic-expression]

(Function1 X)]

(Function2 y ))

(Domonad

[X monadic-expression

Y (m-result (function1 X)]

(Function2 y )))

You do not need to remember these rules when using monad unless you want to create your own monad. You need to remember that (m-result X) represents the monad operation with the value of X. The identity monad and maybe monad we mentioned earlier do not have special monad expressions. At this time, M-result is only the identity function.

Relax now. The monad theory will be discussed in the next section. At that time, I will tell you something about the use of when in. The remainder of this section focuses on programming practices.

We may ask, since clojure already has a let and for, why do we need to create identity monad and sequence monad? The answer is that there are wildcard operations that can be shared in various monads. Using the monad library, you can write a function, take monad as a parameter, and combine multiple operations in a given monad. I will use an abstract example to demonstrate it later. The monad Library also contains many operations that can be used in any monad, whose names start with "M.

The most frequently used monad generic function is m-lift. It converts a function with N parameter values into N monad expressions, the returned value is also a monad expression function. This new function implicitly calls M-bind and M-result. A simple example:

1 (DEF nil-respecting-Addition
2 (with-monad maybe-m
3 (m-lift 2 + )))

 

This function returns the sum of the two parameters, similar to +. The difference is that nil is returned if any parameter is nil. Remember, M-lift must specify the number of parameters required by the function. This information cannot be obtained from the function.

We use domonad to write an equivalent expression to see how m-lift works.

1 (defn nil-respecting-Addition
2 [x y]
3 (domonad maybe-m
4 [A x
5 B y]
6 (+ a B )))

 

M-lift calls M-result and M-bind once for each parameter. In the same definition, if sequence monad is used, a function is returned, which returns a sum of sequence, and its value is calculated from the two input sequence.

Exercise: Which of the following functions is equivalent to clojure's famous built-in functions?

(With-monad sequence-m
(Defn mystery
[F Xs]
(M-lift 1 F) XS )))

 

Wu Yun @: Wu cloud considers this as map from the function structure. Let's give it a try.

(Mystery # (* 2%) [1 2 3 4 5])


Returned results: (2 4 6 8 10), which is exactly the same as map.

 

Another common monad generic function is m-seq. It accepts a sequence composed of a monad expression and returns a sequence of the result value. According to domonad rules, (m-seq [a B C]) is equivalent

(Domonad
[X
Y B
Z C]
'(X y z ))

 

Use the M-seq example. Try it by yourself.

1 (with-monad sequence-m
2 (defn ntuples [n Xs]
3 (m-seq (replicate n XS ))))

 

 

Wu Yun @: let's test it.

(Ntuples 1 [1 2 3]) => (1) (2) (3 ))
(Ntuples 2 [1 2 3]) => (1 1) (1 2) (1 3) (2 1) (2 2 2) (2 3) (3 1) (3 2) (3 3 ))

Because sequence-M is used, we can imagine it is a loop of N layers.

Finally, we will introduce M-chain, which accepts a list composed of single parameter operations. Then, these operations form a chain and each parameter flows through the chain. For example, (m-chain [a B C]) is equivalent

1 (FN [Arg]
2 (domonad
3 [X (A Arg)
4 Y (B x)
5 z (c y)]
6 z ))

 

A common example is hierarchical traversal. The parents function of clojure returns all base classes and interfaces of a class by using multimethod. The following functions are based on parents to find the nth generation ancestor of a class.

1 (with-monad sequence-m
2 (defn n-th-generation
3 [n CLS]
4 (m-chain (replicate n parents) CLS )))
5
6 (n-th-generation 0 (class [])
7 (n-th-Generation 1 (class [])
8 (n-th-generation 2 (class [])

 

Wu Yun @: In this example, N parents operations constitute an operation chain.

You may find that some classes appear more than once in the results because they are the base classes of many classes. In fact, we should use sets instead of sequence to represent the results. This is not difficult. Replace sequence-M with set-M.

In Part3, I will talk about the use of the when condition expression in a loop, and how they are implemented in monad. I will also introduce several other monad expressions.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.