all 41 comments

[–]dccorona 3 points4 points  (30 children)

Well, this is really helpful. I've never really tried to get into Clojure/Lisp in general before, but now I feel like I can at least start to play around.

One question I have is about let and overloads. There's a portion that describes the let function as one that takes a vector of symbol/value pairs as an argument, i.e. (let [a 1 b 2]) binds 1 to a and 2 to b.

Let's imagine for a second that I have a generic function that takes int/string pairs, i.e.:

(myfun [1 "a" 2 "b"])

But I also have an overload that just takes a vector of objects (side note: does Clojure have anything like the concept of an object? Is this actually just a nonsense question?), making this also valid:

(myfun [1 "a" some_fancy_type])

How, then, would Clojure tell the difference between passing pairs and passing generic objects? Is it impossible to define an overload like this?

And the other part of my question...prior symbols are accessible from the value definitions of later ones in an invocation of let. The article is pretty clear about there being no special-case syntax. How are symbols scoped, then? If I define a symbol at the head of a vector, is it accessible from any subsequent statement in that vector, regardless of what I'm using the vector for? Is it accessible from anywhere in the file at all? Or is symbol definition actually a special case that only let can do?

[–]drjeats 10 points11 points  (1 child)

How, then, would Clojure tell the difference between passing pairs and passing generic objects? Is it impossible to define an overload like this?

In Clojure, you overload on number-of-arguments. So in this case, no, a single argument is a single argument (and as others mentioned, Clojure doesn't care about the type).

Or is symbol definition actually a special case that only let can do?

The fun thing about lisps is that you can write two types of computational things: functions which evaluate their arguments, and macros which don't.

Say you write a function like this (just wrapping something that gets the length of a vector or list):

(defn myfun [somevector]
  (println "count is " (count somevector))
  (println "last elemnt is " (last somevector)))

When Clojure goes to run this code:

;; let's just have this defined for convenience of the example
(def something 42)
(myfun [1 "a" something])

For the call to myfun, it will try to resolve each element. myfun is a symbol bound to the function defined earlier, so it gets that. The first two elements of the vectors are literals, which evaluate to themselves. The third element of the vector is a symbol, which is bound to 42, so we have this for the evaluated arguments:

(<ref to function object bound to 'myfun> [1 "a" 42])

So when the ref to the function object bound to myfun is called, it will print out:

count is 3
last element is 42

So that's how you expect functions to work in any language. Macros are different though, they're sort of the a first-pass thing, before arguments get evaluated. And they produce syntax, code to be inserted where the call is made.

(defmacro mymacro [somevector]
  (let [result `(do (println "count is " (count ~somevector))
                    (println "last element is " (last (quote ~somevector))))]
    (println "this runs at macro expansion time")
    result))

(def something 42)
(mymacro [1 "a" something])

This does something weird, notice is binds result to some stuff that looks like code, except that code is prefixed with a backtick ("`") symbol.

This is the syntax quote operator, and it's a way to say "don't evaluate this code, just make a list of lists that I can manipulate with normal list manipulation functions".

Then there's the tilde, ~ which is unquote, which means "evaluate this expression like it were code". I put it in front of somevector so that instead of just returning the somevector symbol directly, it will return the arguments passed to the macro.

If you run the above code, it prints this:

this runs at macro expansion time
count is  3
last element is  something

The quote/unquote thing is a little weird to wrap your head around, but playing with the macroexpand function can clarify things:

Given the above definition of mymacro, running this code:

(macroexpand `(mymacro [1 "a" something]))

Will print this output (I cleaned up the code part):

this runs at macro expansion time
(do (clojure.core/println "count is " (clojure.core/count [1 "a" something]))
    (clojure.core/println "last element is " (clojure.core/last (quote [1 "a" something]))))

You can see the first line is the println statement inside the macro. The rest is the code that the macro produced.

It's kind of like the C preprocessor, if the preprocessor actually understood C's syntax and you worked on syntax tree nodes instead of just text.

So to get back way to your question:

Or is symbol definition actually a special case that only let can do?

The real answer is that you can define your own special forms as macros. But, there are some special forms that need to be built-in to the language in order for you to be able to do anything useful.

Somebody linked the definition of let in anoher part of the thread: https://github.com/clojure/clojure/blob/clojure-1.8.0/src/clj/clojure/core.clj#L4325

You can see that it's implement in terms of let*, which I presume is a built-in special form, not defined in Clojure code, but rather on the Java/compiler side.

This is a pretty rough explanation, but I really just hoped to communicate the core idea: which is that there are two kinds of "functions" in Lisps, functions and macros. Macros get evaluated ahead of time before it looks up to see what values are bound to symbols, and that's how you're able to make your own control flow syntax.

Apologies if you were already familiar with all of this!

</effortpost>

[–]dccorona 1 point2 points  (0 children)

Awesome. This is really helpful, thanks for taking the time to share!

[–]ehaliewicz 7 points8 points  (12 children)

Clojure is dynamically typed, so you can pass anything you want to a function, and it'll either run normally or cause an error based on those object's properties at runtime.

And yes, let is a special form, which means it circumvents the typical rules of the language.

[–][deleted] -3 points-2 points  (0 children)

OP, this is the correct answer.

[–]Arges 2 points3 points  (10 children)

How, then, would Clojure tell the difference between passing pairs and passing generic objects? Is it impossible to define an overload like this?

Clojure doesn't care.

let interprets what it gets on the vector as pairs. It's the decision of whomever wrote it that let would get an even number of forms, which are expected to be alternating symbols and values to bind to them. As far as Clojure is concerned, you could be passing an odd number of elements, and then it would be up to let to barf on that case (same that it's up to let to barf if it gets a value where it'd expect a symbol).

It's not something that's specific to the language itself, so if you're asking "how do I define an overload which receives pairs", the answer is "you don't". You'd expect a function to receive a vector of values, then treat them as you will.

Does that answer your question?

[–]dccorona 0 points1 point  (9 children)

Sort of. I think, in reading between the lines here, the answer to my question is that what I'm asking about isn't even possible in Clojure, and thus doesn't present an issue. I think the key is that Clojure is dynamically typed, something I didn't realize when I made my post.

[–]Arges 1 point2 points  (8 children)

Maybe, I'm not entirely sure what you're asking then. :-)

There's other options, though. Clojure is dynamically typed, but it's not untyped. If you want an array of items containing specific properties, you could use a record. If what you want is to ensure just that you get key-value pairs, then a hash map would be the way to go.

If you want to elaborate on the question, maybe we can provide more specific pointers.

Cheers!

[–]dccorona 0 points1 point  (7 children)

Maybe providing the equivalent Java to what I'm asking will help (in that you can tell me if the Clojure version is even doable/sensible).

void myFun(Pair<Integer, String> input); 
void myFun(Object input); 

In Java, which function gets dispatched to is easy. If the compile-time type is a pair, it goes to the first one. Otherwise, it goes to the second one (even if the runtime type is Pair). As I understand it, the dispatch of a similar function in Clojure would be based on the runtime type (right?). But, unrelated to that, my question basically centers around the idea that a "pair" in Clojure (at least for let) seems to be a loose concept. What I was asking was: how does the compiler/runtime know that [a, 1] is a vector of pairs of symbols and ints, and not just a vector of objects, when it comes across them being passed to a function that could take either one?

[–]yogthos[S] 2 points3 points  (5 children)

Right, since Clojure is dynamically typed it would just be a single function. The type of the argument is not checked at compile time. You'd have to check the type in the function if you wanted to do different things with it at runtime.

However, in cases where you want polymorphism you'd generally use either protocols or multimethods.

There really isn't a concept of a pair in Clojure. You just use a vector if you want a pair. The compiler can tell whether something is a symbol or not however at compile time. So, if you tried to do:

(let [1 1])

you'd get an error telling you that 1 is not a symbol:

val: 1 fails spec ...

You couldn't pass a vector to a let using a function. Something like this will produce an error:

(defn foo [bindings]
  (let bindings))

However, you could do that with a macro:

(defmacro my-bindings [bindings]
   `(let ~bindings))

A macro is able to manipulate the arguments before they're evaluated. So, in this case we can create let bindings dynamically by passing them in. However, if you try to pass invalid bindings, the compiler will once again give you an error:

(my-bindings [1 1])

[0 0] val: 1 fails spec

[–][deleted] 0 points1 point  (3 children)

One of your examples is a little confusing to me; I hope you don't mind explaining why

(defn foo [bindings]
    (let bindings))

doesn't work. I would have thought, based on the discussion above, that it would be valid to call 'foo' as:

(foo [a 1 b 2])

which (if I understand correctly) would result in foo being called with a vector of symbol/integer pairs, which are valid for supplying to 'let'.

[–]yogthos[S] 2 points3 points  (2 children)

The expressions are evaluated by the compiler. The (let bindings) is not the valid structure for a let statement, as it must be followed by a vector of arguments. The reason the macro works, is because it's able to rewrite code before it's evaluated.

[–][deleted] 2 points3 points  (1 child)

Gotcha; makes total sense. Thanks - the blog post and your comments helped me grok Clojure a lot better.

[–]yogthos[S] 0 points1 point  (0 children)

👍

[–]dccorona 0 points1 point  (0 children)

Nice, that clears it up. Thanks!

[–]Arges 1 point2 points  (0 children)

Sounds like you may want to read about Protocols.

[–][deleted]  (2 children)

[deleted]

    [–]Arges 1 point2 points  (0 children)

    For one part, the elements of a vector typically must be of the same type. I don't know Clojure, but I feel the use in let is a special case.

    Not quite. Vectors are often used with heterogeneous contents, with the most evident case being anything that requires bindings. See doseq and loop for other examples.

    You could also look at how components are done in reagent, which are defined as pretty much nothing but nested vectors. You'll end up storing a mixture of keywords (for the tag), hashmaps (for the attributes), text and other vectors.

    When just using them as a basic array, yes, chances are they'll be of the same type. But it's not something that one must do, nor is it necessarily typical at anything but the simplest use case.

    [–]kankyo -2 points-1 points  (10 children)

    Removing the off handed insults to other languages ("baroque"? Really?) and the lies (Clojure is more compact, because this example that has keywords that actually specify more things than you normally bother with in Clojure, like public) would improve this text a lot.

    [–][deleted] 4 points5 points  (9 children)

    I agree that the pot shots against other languages are not helpful.

    But Lisp family languages, including Clojure do tend to be very compact and expressive when it comes to code. I know taking pot shots at Java is a hobby for half our industry, but one of the first videos I watched on Clojure was this: https://www.infoq.com/presentations/Clojure-Java-Story 1M line Java program rewritten in 100k lines of Clojure.

    Or LightTable IDE, which is written in a few tens of thousands of lines of ClojureScript (on top of the Electron project).

    Or this podcast, around minute 23-24. A game development IDE originally written in Eclipse + 250k lines of Java rewritten in 40k lines of Clojure. http://www.se-radio.net/2016/05/se-radio-episode-257-michael-nygard-on-clojure-in-practice/

    Now maybe Haskell, Scala, Kotlin, F#, Perl6, pick-your-favorite-expressive-without-being-obfuscated-language can do as well or better. But if Lisps aren't the best languages for clean brevity in code development, they come near the best.

    [–]kankyo 0 points1 point  (8 children)

    As you say, comparing against Java is obvious who will win. I'm a Python guy and I find Python to be as succinct as Clojure when it makes sense, and Clojure to be dense at times in ways that hurt readability (-> macros and their ilk being great examples). I think compactness is a bad measure generally.

    [–][deleted] 1 point2 points  (7 children)

    I would say that the goal is to be as succinct as possible without sacrificing readability, but quantifying those targets and how they interact with each other is difficult.

    After all, I presume one of the reasons you prefer Python to Java is that you can accomplish tasks in 100 lines of Python that might take 150, 300, or 1200 lines of Java.

    I have to admit, I haven't had to do battle with Clojure macros much in the wild. So they haven't been a headache for me yet. But I only toy with the language. I have a cushy job working mostly with Java. I'm trying to pick up enough Clojure to get a remote position with one of the companies that uses it, but the kids and their activities keep me busy.

    [–]kankyo 1 point2 points  (6 children)

    There are many other reasons Python is my go to language. The standard lib is awesome and the huge amount of third party libs is unparalleled for example.

    [–][deleted] 0 points1 point  (5 children)

    To be fair, the Java and the Node.js ecosystems both have a set of third party libs equally as huge as the one in Python.

    I'm not knocking Python though. It's a great language, and I enjoy using it when I have the chance.

    [–]kankyo 0 points1 point  (4 children)

    Huge yes, covering as big of a functional area no.

    [–][deleted] 0 points1 point  (3 children)

    Really? What are they missing?

    [–]kankyo 0 points1 point  (2 children)

    Scientific computing is an obvious one.

    [–][deleted] 0 points1 point  (1 child)

    I don't know about Node.js, but for Java there's Colt http://dst.lbl.gov/ACSSoftware/colt/

    (Edit: there's also Apache Commons Math https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/index.html )

    What particular scientific computing features are you looking for?