Programming clojure-multimethods

Source: Internet
Author: User
Document directory
  • Living without multimethods
  • Defining multimethods
  • Dispatch by class
  • Dispatch Is Inheritance-aware
  • Moving beyond simple dispatch
  • Creating ad hoc taxonomies
  • Adding inheritance to ad hoc types

Multimethods is actually the pattern matching mentioned in the FP basics. To put it bluntly, we define different logics based on different parameters.
The first thing I think of is function overloading,
Http://www.cnblogs.com/skynet/archive/2010/09/05/1818636.html
Parameter quantity overloadFor this clojure function, you can define multiple sets of parameter lists as follows.

(defmacro and  ([] true)  ([x] x)  ([x & rest]    `(let [and# ~x]     (if and# (and ~@rest) and#))))

Overload of parameter type, Which is difficult for weak languages,

Multimethods (dispatch by class) is required for clojure.

It is more troublesome to implement python,

This is the Python version of multimethods implemented by Guido van rosum,Http://www.artima.com/weblogs/viewpost.jsp? Thread = 1, 101605

Of course, multimethods is not as simple as function overloading,

Multimethods is similar to Java polymorphism but more general

PolymorphismIs the ability of a function or method to have different definitions depending on the type of the target object.

Multimethods can not only achieve this, but also make it more general. For example, we can select different logic (dispatch by ad hoc type) through the judgment of values (or multiple conditions)

What problems does Polymorphism or object-oriented solution solve?

Use higher abstraction to solve the clutter and bloated process-oriented code.

Most of the reasons for this disorder are due to a large number of constantly changing if-else...

Object-oriented language encapsulates the branch logic in a large number of classes, but it still cannot avoid if-else because there is no encapsulation judgment condition.

You still need to call functions of different classes under different conditions, or use polymorphism to bind different subclass objects to the base class pointer.
For example, in the factory mode, you still need to create different factory classes under different circumstances.

You can use eval (both Python and clojure) to solve this problem. In fact, the judgment condition is encapsulated in the class name.

Therefore, it is clear that multimethods is actually solving this problem and better.

He does not need to use a heavyweight (high overhead) class to solve such problems, but can directly use functions.

Well-encapsulated judgment conditions can automatically select the appropriate function based on the judgment conditions.

Living without multimethods

For example, we can implement the function my-print that can print different types of data.

Because the logic of print of different types is different, if-else is required.

(Defn my-print [ob] (cond (vector? Ob) (my-print-vector ob); to make the example clear, do not list the specific implementation of my-print-vector (nil? Ob) (. Write * out * "nil") (string? Ob) (. Write * out * ob )))

This problem is not easy to maintain. Every time a new type is supported, You need to modify my-print. If more types are supported, the code is clear and maintained.

 

Defining multimethods

How to define a multimethod is not easy to explain in two steps

If you think of switch... Case, dispatch-FN in defmulti is actually the computing logic in the switch.

The dispatch-Val in defmethod is the value in case.

To define a multimethod, use defmulti:

(Defmulti name dispatch-FN)

To add a specific method implementation to my-println, use defmethod:

(Defmethod name dispatch-Val & FN-tail)

 

Dispatch by class

In the above example, we can simply write it as this solves the same problem as function overload.

(defmulti my-print class)    ;switch (class(s))(defmethod my-print String [s]  ; case: String  (.write *out* s))(defmethod my-print nil [s]    ;case: nil  (.write *out* "nil" ))(defmethod my-print vector [s]  (my-print-vector s))
(Defmethod my-print: default [s]; Switch... Case also requires default (. Write * out * "# <") (. Write * out * (. tostring s) (. Write * out * "> "))
Dispatch Is Inheritance-aware

Clojure is based on Java, so there are traces of OO everywhere...

Multimethod dispatch knows about Java inheritance.

(Defmethod my-print number [N] (. Write * out * (. tostring n) (my-println 42); no error is reported: Int Is Not Number 42

42 is an integer, not a number. multimethod dispatch is smart enough to know that an integer is a number and match anyway.

(isa? Integer Number) true
Moving beyond simple dispatch

Dispatch by class has a problem, that is, multi-inheritance.

What should I do when dispatch matches two defmethods at the same time?

Example,

(Defmethod my-print Java. util. collection [c] (. write * out *"(")(. write * out * (STR-join "" C ))(. write * out * ") (defmethod my-print clojure. lang. ipersistentvector [c]; displays the special format of vector (. write * out *"[")(. write * out * (STR-join "" C ))(. write * out * "]")

An error is reported when the following call is performed, because the vector inherits from the collection and ipersistentvector multiple times.

(My-println [1 2 3])

Java. Lang. illegalargumentexception: multiple methods match dispatch value:

Class clojure. Lang. lazilypersistentvector-> interface clojure. Lang. ipersistentvector and interface java. util. Collection,

And neither is preferred

 

The solution of clojure is to specify the preferred relationship through perfer-method.

Limits limits ages constrain method dispatch to make sure these conflicts never happen, such as by forbidding multiple

Inheritance. clojure takes a different approach. You can create conflicts, and you can resolve themPrefer-Method:

(Prefer-method multi-name loved-Dispatch dissed-dispatch)

(Prefer-Method

My-print clojure. Lang. ipersistentvector java. util. Collection)

 

Creating ad hoc taxonomies

The power of multimethods is not only dispatch by class, but also dispatch by ad hoc type.

For example, a bank account is defined. The tag is divided into two types: checking, saving, and balance.

(ns examples.multimethods.account)(defstruct account :id :tag :balance)

Define two keywords in the current namespace

::Checking:examples.multimethods.account/Checking::Savings:examples.multimethods.account/Savings

TheCapital namesAre a clojure conventionto show the keywords are acting as types.

TheDoubled ::Causes the keywords to resolve in the current namespace.

For ease of use, define the namespace abbreviation,

(alias 'acc 'examples.multimethods.account)

The following defines a simple interest rate calculation application. The logic can be determined by parameter values.

(defmulti interest-rate :tag)(defmethod interest-rate ::acc/Checking [_] 0M)(defmethod interest-rate ::acc/Savings [_] 0.05M)

Then we can implement a complicated computing annual fee application. We can see that multimethods is powerful.

• Normal checking accounts pay a $25 service charge.

• Normal savings accounts pay a $10 service charge.

• Premium accounts have no longer.

• Checking accounts with a balance of $5,000 or more are premium.

• Savings Accounts with a balance of $1,000 or more are premium.

The annual fee is different for current and savings accounts.

First, the function of whether to pay the fee is implemented, and the logic is still selected through the value.

(defmulti account-level :tag)(defmethod account-level ::acc/Checking [acct]  (if (>= (:balance acct) 5000) ::acc/Premium ::acc/Basic))(defmethod account-level ::acc/Savings [acct]  (if (>= (:balance acct) 1000) ::acc/Premium ::acc/Basic))

The annual fee function must be implemented based on the tag type and account-level conditions.

Multimethods can be combined to determine multiple conditions, which is very powerful,

(defmulti service-charge (fn [acct] [(account-level acct) (:tag acct)]))(defmethod service-charge [::acc/Basic ::acc/Checking] [_] 25)(defmethod service-charge [::acc/Basic ::acc/Savings] [_] 10)(defmethod service-charge [::acc/Premium ::acc/Checking] [_] 0)(defmethod service-charge [::acc/Premium ::acc/Savings] [_] 0)

Adding inheritance to ad hoc types

There is one further improvement you can make to service-charge.

You can also perform one step of optimization to merge the last two defmethods into one, because as long as the result is: ACC/premium, the result is 0.

The method used is,

Clojure lets you define arbitrary parent/child relationshipsDerive:

(Derive child parent)

(derive ::acc/Savings ::acc/Account)(derive ::acc/Checking ::acc/Account)(defmethod service-charge [::acc/Premium ::acc/Account] [_] 0)

I personally think this method is not very good. In fact, the implementation mechanism can directly ignore the second condition.

 

When should I use multimethods?

I have mentioned a lot in this article,

First, multimethods is not used in clojure, especially by ad hoc type, with fewer

I personally think it is not so absolute. This mechanism is not used to implement simple if-else replacement or function overloading, and it is not convenient to use.

Therefore, when you really need it, you are willing to pay the tedious cost for using it, that is, when you should use multimethods...

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.