- Creating Functions
- Variadic Functions
- Multi-Arity Functions
- Anonymous Functions
- Applying Functions
- Locals and Closures
- Functions with preconditions
- Argument type hints
- Collections and Keywords as functions
- Function resolved from a string
- Partial Functions
- Function Composition
- Function threading macros
- Functions calling each other
(do
(defn add [x y] (+ x y))
(def mul (fn [x y] (* x y)))
(add 1 2)
(mul 3 4)
(let [f (fn [x y] (- x y))]
(f 5 3)))
A variadic function is a function of indefinite arity, accepting a variable number of arguments. A variadic function can have any number of fixed arguments.
(do
;; variadic sum with a single fixed arg
(defn sum [x & xs]
(reduce + x xs))
(sum 1)
(sum 1 2)
(sum 1 2 3 4 5 6 7 8 9 10))
(do
(defn arity
([] (println "arity 0"))
([a] (println "arity 1"))
([a b] (println "arity 2"))
([a b c] (println "arity 3"))
([a b c & z] (println "arity 3+")))
(arity 1 2))
Values can be passed by name using a map literal.
The function foo
expects the named arguments x
and y
. The :or
clause specifies
a default for y
.
(do
(defn foo [{:keys [x y] :or {y 10}}]
(list x y))
(foo {:x 1 :y 2}) ;; => (1 2)
(foo {:x 1})) ;; => (1 10)
(do
(map (fn [x] (* 2 x)) (range 0 5)) ; => (0 2 4 6 8)
(map #(* 2 %) (range 0 5)) ; => (0 2 4 6 8)
(map #(* 2 %1) (range 0 5)) ; => (0 2 4 6 8)
(let [f #(+ %1 %2 %3)] (f 1 2 3)) ; => 6
((fn [x] (* x 10)) 5)) ; => 50
The apply
function invokes a function with 0 or more fixed arguments, and draws
the rest of the needed arguments from a list or a vector. The last argument must
be a list or a vector.
(do
(apply max [1 5 2 8 3]) ;; same as (max 1 5 2 8 3)
(apply str '(1 2 3 4)) ;; same as (str 1 2 3 4)
(apply str 1 '(2 3 4)) ;; same as (str 1 2 3 4)
(apply str 1 2 '(3 4)) ;; same as (str 1 2 3 4)
(apply str 1 2 3 '(4)) ;; same as (str 1 2 3 4)
let
binds symbols to values in a "lexical scope". A lexical scope creates a
new context for names, nested inside the surrounding context. Names defined
in a let
take precedence over the names in the outer context.
(let [x 1
y (* 2 3)]
(+ x y))
This let
expression creates two local bindings for x
and y
. The expression
(+ x y)
is in the lexical scope of the let
and resolves x
to 1 and y
to 6.
Outside the "lexical scope" (let
expression), x
and y
will not
be accessible.
defn
binds its function arguments in a new "lexical scope" as well. Unlike let
the local bindings for x
and y
get their values from the caller.
(do
(defn sum [x y]
(+ x y))
(sum 1 2))
The fn
special form creates a "closure". It "closes over" the surrounding
lexical scope and captures their values beyond the lexical scope.
A closure is a function that remembers the environment at which it was created.
(do
(defn pow [n]
(fn [x] (apply * (repeat n x)))) ; closes over n
;; n is provided here as 2 and 3, then n goes out of scope
(def square (pow 2))
(def cubic (pow 3))
;; n value still available because square and cubic are closures
(square 4) ; => 16 effectively as (apply * (repeat 2 4))
(cubic 4)) ; => 64 effectively as (apply * (repeat 3 4))
Even global functions can remember the context they have been created:
(do
(let [x 100]
(defn test [] (str "x: " x)))
(test)) ; => "x: 100"
(do
(defn sum [x y]
{ :pre [(number? x) (number? y)] }
(+ x y)))
Venice supports function argument type hints through argument metadata. Type hints are available with Venice 1.11.x.
(do
(defn sum [^:long x ^:long y] (+ x y))
(sum 1 2))
(do
(defn sum [^:number x ^:number y] (+ x y))
(sum 1 2)
(sum 1.0 2)
(sum 1.1M 2.6M))
(do
(ns foo)
(deftype :complex [real :long
imaginary :long])
(defn sum [^:foo/complex x ^:foo/complex y]
(complex. (+ (:real x) (:imaginary x))
(+ (:real y) (:imaginary y))))
(sum (complex. 1 2) (complex. 5 8)))
Type hints with multi-arity functions:
(do
(defn foo
([] 0)
([^:long x] x)
([^:long x ^:long y] (+ x y))
([^:long x ^:long y & xs] (apply + x y xs)))
(foo )
(foo 1)
(foo 1 2)
(foo 1 2 3 4 5))
Type hints with sequential destructuring:
(do
(defn foo [[^:long x ^:long y]] (+ x y))
(foo [1 2]))
Type hints with associative destructuring:
(do
(defn foo [{:keys [^:long x ^:long y]}] (+ x y))
(foo {:x 1 :y 2}))
For datatypes of the core namespace the namespace can be omitted.
;; these two function definitions are equivalent
(defn sum [^:long x ^:long y] (+ x y)))
(defn sum [^:core/long x ^:core/long y] (+ x y)))
Vectors, maps, sets, and keywords are functions too.
Vectors
(do
([1 2 3] 1) ; -> 2
;; with default
([1 2 3] 5 10)) ; -> 10
Maps
(do
;; instead of (get {:a 1 :b 2} :b)
({:a 1 :b 2} :b) ; -> 2
({:a 1 :b 2} :c) ; -> nil
;; with default
({:a 1 :b 2} :c 9) ; -> 9
;; accessing nested maps
(let [m {:a {:b {:c 3}}}]
(-> m :a :b :c))) ; -> 3
Sets
(do
(#{:a :b} :b) ; -> :b
(#{:a :b} :c) ; -> nil
;; with default
(#{:a :b} :c 9)) ; -> 9
Keywords
(do
(:b {:a 1 :b 2}) ; -> 2
(:c {:a 1 :b 2}) ; -> nil
(:b #{:a :b}) ; -> :b
(:c #{:a :b}) ; -> nil
;; with defaults
(:c {:a 1 :b 2} 9) ; -> 9
(:c #{:a :b} 9)) ; -> 9
(let [add (resolve (symbol "+"))]
(add 2 5))
In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.
(do
(defn add [x y] (+ x y))
(def add2 (partial add 2))
(def add3 (partial add 3))
(add2 4) ;; => 6
(add3 4)) ;; => 7
If we did need to call add
(adding two numbers) with fewer than the required
arguments, for example if we are mapping add
over a vector, then we can use
partial
to help us call the add
function with the right number of arguments:
(do
(defn add [x y] (+ x y))
(map (partial add 2) [1 2 3 4])) ;; => (3 4 5 6)
In this case the partial function prevents us from writing an explicit anonymous
function like #(+ 2 %)
in (map #(+ 2 %) [1 2 3 4])
(map (partial + 2) [1 2 3 4]) ;; => (3 4 5 6)
The reduce
function can only work on a single collection as an argument (or a value
and a collection), so an error occurs if you wish to reduce over multiple collections.
(reduce + [1 2 3 4]) ;; => 10
This returns an error due to invalid arguments:
(reduce + [1 2 3 4] [5 6 7 8]) ;; error
However, by using partial
we can take one collection at once and return the result
of reduce on each of those collections:
(map (partial reduce +) [[1 2 3 4] [5 6 7 8]]) ;; => (10 26)
Creates a new function composed of one or more functions. The composed functions are executed from right to left.
(comp not zero?)
is equivalent to (fn [x] (not (zero? x))
(filter (comp not zero?) [0 1 0 2 0 3 0 4]) ;; => [1 2 3 4]
(do
(def person
{:name "Peter Meier"
:address {:street "Lindenstrasse 45"
:city "Bern"
:zip 3000}})
((comp :street :address) person) ;; => "Lindenstrasse 45"
((comp :private :email) person)) ;; => nil
(do
(def xform
(comp
(partial take 4)
(partial map #(+ 2 %))
(partial filter odd?)))
(xform (range 0 10))) ;; => (3 5 7 9)
Threading macros in Venice are used to make code more readable and to simplify the chaining of operations. They help to “thread” an initial value through a series of functions or forms. There are two main threading macros in Venice:
1. -> (thread-first macro)
2. ->> (thread-last macro)
Practical Use Cases:
1. Data Processing Pipelines: Threading macros are ideal for building data
processing pipelines where each step transforms the data in a clear and
readable manner.
2. Nested Function Calls: They help in avoiding deeply nested function calls,
making the code easier to read and maintain.
3. Combining Transformations: When you have a series of transformations to
apply to data, threading macros keep the sequence of operations clear.
Thread first ->
Taking an initial value as its first argument, ->
threads it through one or more
expressions. Starting with the second form, the macro inserts the first value as
its first argument and repeats inserting the result of the form to the first argument
of the next form.
(do
(defn bigint [x] (. :java.math.BigInteger :new x))
(-> (bigint "1000")
(. :multiply (bigint "600"))
(. :add (bigint "300")))) ;; => 600300
(do
(def person
{:name "Peter Meier"
:address {:street "Lindenstrasse 45"
:city "Bern"
:zip 3000}})
(-> person :address :street) ;; => "Lindenstrasse 45"
(-> person :email :private)) ;; => nil
Thread last ->>
Taking an initial value as its first argument, ->>
threads it through one or more
expressions. Starting with the second form, the macro inserts the first value as
its last argument and repeats inserting the result of the form to the last argument
of the next form.
(->> (range 0 8)
(filter odd?)
(map #(+ 2 %))) ;; => (3 5 7 9)
Thread any as->
, -<>
; allows to use arbitrary positioning of the argument
(as-> (range 0 8) v
(filter odd? v)
(reduce + v)
(* v 2)) ;; => 32
; the chosen threading symbol may be used multiple times in a form
; thus allowing the use of complex forms like if expressions
(as-> {:a 1 :b 2} m
(update m :a #(+ % 10))
(if true
(update m :b #(+ % 10))
m)) ;; => {:a 11 :b 12}
; allows to use arbitrary positioning of the argument using the placeholder '<>'
; note: the threading symbol <> may only be used once in a form
(-<> (range 0 8)
(filter odd? <>)
(reduce + <>)
(* <> 2)) ;; => 32
Venice supports functions calling each other without needing to declare them
or bind them with letfn
as required with Clojure.
Nevertheless alternately calling functions can cause stack overflows if the
recursion is too deep. Use the trampoline
function or convert to self-recursion
to overcome the stack overflow problem.
(do
(let [print-number (fn [n]
(println n)
(decrease n))
decrease (fn [n]
(when (> n 0)
(print-number (dec n))))]
(decrease 10)))