clojusc/defun

A Clojure macro supporting functions with pattern matching heads a la LFE


License
EPL-1.0

Documentation

defun

Build Status Dependencies Status Clojars Project Tag Clojure version

A Clojure macro supporting functions with pattern matching heads a la LFE

About

The macros defined by this project allow one to write code a little more closely to LFE (a Lisp that runs and inter-operates with Erlang and other BEAM languages).

Note that this repo was originally cloned from here.

Usage

Requiring

Require defun.core in clojure:

(require '[defun.core :refer [defun]])

Or refer-macros in clojurescript:

(ns cljs-test
  (:require  [defun.core :refer-macros [defun]])
(enable-console-print!)

Patterns

(defun say-hi
  ([:dennis] "Dennis, there's some lovely filth down here!")
  ([:sir-robin] "That's ... that's enough music for now lads, there's dirty work afoot.")
  ([:zoot] "No, I am Zoot's identical twin sister, Dingo.")
  ([other] (str  other ", look at the BOOOOONES!")))

Then calling say-hi with different names:

(say-hi :dennis)
;;  "Dennis, there's some lovely filth down here!"
(say-hi :sir-robin)
;;  "That's ... that's enough music for now lads, there's dirty work afoot."
(say-hi :zoot)
;;  "No, I am Zoot's identical twin sister, Dingo."
(say-hi "Tim")
;;  "Tim, look at the BOOOOONES!"

We can use all patterns that supported by core.match.

For example, matching literals:

(defun test1
    ([true false] 1)
    ([true true] 2)
    ([false true] 3)
    ([false false] 4))

(test1 true true)
;; 2
(test1 false false)
;; 4

Matching sequence:

(defun test2
    ([([1] :seq)] :a0)
    ([([1 2] :seq)] :a1)
    ([([1 2 nil nil nil] :seq)] :a2))

(test2 [1 2 nil nil nil])
;; a2

Matching vector:

(defun test3
    ([[_ _ 2]] :a0)
    ([[1 1 3]] :a1)
    ([[1 2 3]] :a2))

(test3 [1 2 3])
;; :a2

Recursion

Let's move on, what about define a recursive function? That's easy too:

(defun count-down
  ([0] (println "Reach zero!"))
  ([n] (println n)
     (recur (dec n))))

Invoke it:

(count-down 5)
;;5
;;4
;;3
;;2
;;1
;;Reach zero!
nil

An accumulator from zero to number n:

(defun accum
  ([0 ret] ret)
  ([n ret] (recur (dec n) (+ n ret)))
  ([n] (recur n 0)))

(accum 100)
;;5050

A fibonacci function:

(defun fib
    ([0] 0)
    ([1] 1)
    ([n] (+ (fib (- n 1)) (fib (- n 2)))))

Output:

(fib 10)
;; 55

Guards

Added a guard function to parameters:

(defun funny
  ([(N :guard #(= 42 %))] true)
  ([_] false))

(funny 42)
;;  true
(funny 43)
;; false

Another function to detect if longitude and latitude values are both valid:

(defun valid-geopoint?
    ([(_ :guard #(and (> % -180) (< % 180)))
      (_ :guard #(and (> % -90) (< % 90)))] true)
    ([_ _] false))

(valid-geopoint? 30 30)
;; true
(valid-geopoint? -181 30)
;; false

Compatibility with defn

Try to define function just like defn:

(defun hello
   "hello world"
   [name] (str "hello," name))
(hello "defun")
;; "hello,defun"

defun also supports variadic arguments, doc, metadata etc.

Additionally, a defun- is provided for creating private functions a la defn-.

lambda and flet

There are also two macros lambda and flet that provide pattern matching, but in all other ways are like the clojure.core/fn and clojure.core/letfn functions, respectively.

((lambda
    ([[_ _ 2]] :a0)
    ([[1 1 3]] :a1)
    ([[1 2 3]] :a2))
  [1 2 3])
;; :a2

(flet [(test3 ([[_ _ 2]] :a0)
                    ([[1 1 3]] :a1)
                    ([[1 2 3]] :a2))]
  (test3 [1 2 3]))
;; :a2

Note that, if you are coming from LFE, in this project's lambda is actually like LFE's match-lambda.

Criterium benchmarking

Uses the above function accum compared with a normal clojure function:

(require '[criterium.core :refer [bench]])

(defn accum-defn
    ([n] (accum-defn 0 n))
    ([ret n] (if (= n 0) ret (recur (+ n ret) (dec n)))))

(defun accum-defun
  ([0 ret] ret)
  ([n ret] (recur (dec n) (+ n ret)))
  ([n] (recur n 0)))

(bench (accum-defn 10000))
;;Evaluation count : 210480 in 60 samples of 3508 calls.
;;             Execution time mean : 281.095682 µs
;;    Execution time std-deviation : 2.526939 µs
;;   Execution time lower quantile : 277.691624 µs ( 2.5%)
;;   Execution time upper quantile : 286.618249 µs (97.5%)
;;                   Overhead used : 1.648269 ns

(bench (accum-defun 10000))
;;Evaluation count : 26820 in 60 samples of 447 calls.
;;             Execution time mean : 2.253477 ms
;;    Execution time std-deviation : 13.082041 µs
;;   Execution time lower quantile : 2.235795 ms ( 2.5%)
;;   Execution time upper quantile : 2.281963 ms (97.5%)
;;                   Overhead used : 1.648269 ns

accum-defn is much faster than accum-defun ... pattern-matching in Clojure does have a cost; trade-offs should be analyzed before using.

License

Copyright © 2014 Dennis Zhuang

Distributed under the Eclipse Public License either version 1.0 or (at

your option) any later version.