I often find myself writing functions like this:
(defn my-function [arg]
(let [x (foo arg)
y (bar x)
z (baz x)]
(make-result y z)))
So instead of writing (make-result (bar (foo arg)) (baz (foo arg))) I split the code into a set of temporary bindings. (Of course, typically the bindings are more complicated than this trivial example.)
I don't particularly like that the core function logic is contained inside the let bindings.
Is there a better convention for writing such sequences of code?
This is idiomatic, and let is explicitly sequential and expected to be used that way. Especially since this avoids calling (foo arg) twice (Clojure is not so pure that the compiler could automatically transform this to a single call to foo).
Related
I'm optimizing a small performance focused section of an application. I'm attempting to create a Java array of a type created via deftype
(deftype MyThing [foo bar baz])
However, I can't seem to find any documentation on how to type hint these arrays, and without a type hint, reflection occurs.
(def my-array (make-array MyThing 10))
(aget my-array 0)
Gives the warning:
Reflection warning, call to static method aget on clojure.lang.RT can't be resolved (argument types: core.MyThing, int).
Is there a way to correctly hint the type?
I'm surprised to not find any question on this topic on Stack Overflow, and still more surprised to see that the official documentation for adding type hints does not cover this - I was sure I'd be able to close this as a duplicate, or link to official documentation, but here I am typing it out by hand like a barbarian.
The "primitive" version of type hinting, for which other forms are mere shorthand1, is
^"Foo" xs
where Foo is the JVM's internal name for the class you want to hint. There are shorthands for primitive arrays (^ints xs), and for ordinary class types (^MyType x), but this doesn't cover arrays of non-primitive types. For that, you have to know the official class name for your type. You could look up the rules for this, but the simplest thing to do is to just ask the interpreter!
user=> (defrecord Foo [])
user.Foo
user=> (def foos (make-array Foo 10))
#'user/foos
user=> (class foos)
[Luser.Foo;
user=> (aget foos 0)
Reflection warning, null:1:1 - call to static method aget on clojure.lang.RT can't be resolved (argument types: unknown, int).
nil
user=> (aget ^"[Luser.Foo;" foos 0)
nil
1 Actually, even more primitive is ^{:tag "Foo"}, but this distinction doesn't matter for this question.
If the argument is a symbol return it. If the argument is a list call another method.
New to Clojure and cannot find the canonical way to do it. In Python, one could do:
def return_on_arg_type(arg):
if type(arg) is str:
return arg
if type(arg) is list:
return another_method(arg)
May be I could use multi-methods but how to match on type of the argument and also is matching on type acceptable in Clojure?
There's essentially 3 dispatch methods in Clojure:
Use cond combined with predicates (that's methods that return true or false and often have a name ending in ?) as Alan has described.
Use Protocols which dispatch on the type of the first argument. The reference documentation for this would be at https://clojure.org/reference/protocols
Multimethods. You can think of Multimethods as a programmable dispatch method. They can do much more than just look at the type of their arguments, they could also look into arguments, count elements on a vector argument and much more. Canonical documentation at https://clojure.org/reference/multimethods
Take a look at Clojure multimethods vs. protocols for a short discussion on Multimethods vs Protocols.
Not sure of the goal, but this should work:
(defn something [arg]
(cond
(str? arg) arg
(list? arg) (other-fn arg)))
It is ok to check for types in Clojure. It is not an un-typed language.
The code proposed by Alan is idiomatic IMHO if you need a simple dispatch mechanism.
There are other possibilities here. One you mentioned already: multi-methods.
Your example could be written as
(defmulti something class [arg])
(defmethod something String [arg] arg)
(defmethod something java.util.List [arg] (some-other arg))
The dispatch fn is class in this case, but you can implement any kind of dispatch condition.
There's also clojure.match, a general purpose matching library (among others). See https://github.com/clojure/core.match
Next to using a multimethod or a method to explicitly check, it's also possible to dispatch on the type of first argument with a protocol:
(defprotocol OnArgTypeReturner
(return-on-arg-type [arg]))
(extend-protocol OnArgTypeReturner
String
(return-on-arg-type [arg] arg)
clojure.lang.IPersistentList
(return-on-arg-type [arg] "another-method-called"))
(return-on-arg-type "foo")
;; => "foo"
(return-on-arg-type '(1 2 3))
;; => "another-method-called"
common lisp and clojure do
(defn function-name (arg1 arg2) (body))
racket/scheme does
(defn (function-name arg1 arg2) (body))
The latter makes more sense to me because the syntax for function definition and invocation is similar. What's the reasoning for the former?
TL;DR It's not quite as simple as you put it. The syntax is slightly more different and every time someone create a language they get to decide new syntax, usually to make it more concise that previous languages or the language has a design that requires it. Of course what is "better" is a matter of opinion.
In Scheme (and the descendant Racket) you have one namespace for both variables and functions. Thus you use define for everything.
(define variable 10)
(define function (lambda (arg1 arg2) body ...))
Now define has a short cut for that last one, which is:
(define (function arg1 arg2) body ...)
So the rule is if the first part is a pair? it is supposed to be expanded to the longer form with lambda. That it resembles an application is just a conicident I guess. This extra feature is just to save some keystrokes and is often omitted in books like The little Schemer since its confusing to have two ways and the learner and they might think defining a binding for a function is more special than defining 10, which is ridiculous. The rest argument usually confuses. eg. these are the same:
(define (test . x) x)
(define test (lambda x x))
In Common Lisp you have two namespaces and thus defun is only used for global scope and there are function equivalents of variable forms that makes functions. in defun you have a list first argument in Common Lisp too, but it does something completely different:
(defun (setf name) (new-value)
body ...)
This provides a way to get the same symmetry as other accessors in CL. eg. if (car x) gets the car value from a cons (setf (car x) 10) will alter it. In my example name can be used in the same manner as car. It's quite handy.
Clojure does it with def and fn and uses an array for parameters:
(def function (fn [arg1 agr2] body ...))
defn is just a shorthand version just like the define that starts with a pair. How it didn't end up more similar to Scheme is perhaps either the fact that the parameter data is an array or that they kept syntax as close to the original as possible. If I remember correctly you can have a function name as second argument with fn as well making fn and defn look almost identical.
The latter makes more sense to me because the syntax for function definition and invocation is similar.
In Common Lisp the function definition generally is not similar to the invocation, thus it makes no sense to pretend they do.
This is a valid function defintion:
(defun foo (arg1 arg2
&optional (ovar 10)
&rest args
&key (ak 11 a-p)
&allow-other-keys
&aux (aux1 12))
(list arg1 arg2 ovar args ak a-p aux1))
These are valid calls for above:
(foo 1 2)
(foo 1 2 3)
(foo 1 2 3 :ak 4)
(foo 1 2 3 :foo 10 :ak 4)
Thus the call arguments look different from the definition argument list.
In the case of DEFMETHOD a definition may look like:
(defmethod (setf foobar) :around ((value integer) (object foo))
(setf (slot-value object 'a) value))
and a call would be
(setf (foobar object) 10)
Summary
In Common Lisp function definitions won't look like calls anyway
Excursion
It's simple to define a Scheme-like syntax for simple CL functions:
(defmacro define ((name &rest args) &body body)
`(defun ,name ,args ,#body))
CL-USER 36 > (macroexpand-1 `(define (foo a b c) (baz) (bar)))
(DEFUN FOO (A B C) (BAZ) (BAR))
T
Some programs even use something similar...
Function definition is just an operation in the Lisp family. There is actually little meaning in making function definition similar to function application.
With clojure functions, I can define:
(defn f [x & xs] (apply some-function x xs))
I'm attempting to do this same sort of thing with a protocol, e.g.
(defprotocol foo
(bar [f])
(baz [f & gs]))
This compiles (at least in the REPL), but any implementing type seems to fail on this (the variadic, baz) method. Is this officially not supported? The sources that I've consulted are silent.
This is not supported, for the reasons Stuart Sierra gives. To go into a little more detail, the & symbol is special only in a destructuring context like let or function arguments. As Stuart makes clear, defprotocol is not such a context.
But & is still a legal symbol, so you've defined a protocol with two functions: bar takes one argument, named f, and baz takes three, named f, &, and gs.
As answered Stuart Sierra in following thread, variadic methods aren't supported, and possibly will not supported in the future
With clojure functions, I can define:
(defn f [x & xs] (apply some-function x xs))
I'm attempting to do this same sort of thing with a protocol, e.g.
(defprotocol foo
(bar [f])
(baz [f & gs]))
This compiles (at least in the REPL), but any implementing type seems to fail on this (the variadic, baz) method. Is this officially not supported? The sources that I've consulted are silent.
This is not supported, for the reasons Stuart Sierra gives. To go into a little more detail, the & symbol is special only in a destructuring context like let or function arguments. As Stuart makes clear, defprotocol is not such a context.
But & is still a legal symbol, so you've defined a protocol with two functions: bar takes one argument, named f, and baz takes three, named f, &, and gs.
As answered Stuart Sierra in following thread, variadic methods aren't supported, and possibly will not supported in the future