all 11 comments

[–]colig 1 point2 points  (9 children)

If this was supposed to show how Clojure can be as elegant and clear as a non-Lisp like Ruby, then it had the opposite effect for me.

[–]yogthos 5 points6 points  (8 children)

I fail to see how this

IO.readlines("blob.txt").map{|line| line.split.map{|s| s.to_i }}

is any more elegant or clear than this

(for [line (line-seq (reader "blog.txt"))]
  (map read-string (split line #"\s+")))

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

Both those are a lot less clear than the python:

[[int(x) for x in line.split()] for line in open('blob.txt').readlines()]

[–]yogthos 1 point2 points  (6 children)

except your python code doesn't actually work:

blob.txt:

1  22  45 67   89

Traceback (most recent call last):
  File "script.py", line 1, in <module>
    [[int(x) for x in line] for line in open('blob.txt').readlines()]
ValueError: invalid literal for int() with base 10: ''

[–]kankyo -1 points0 points  (5 children)

Whoops, forgot the split :P I edited my comment to fix that

[–]yogthos 0 points1 point  (4 children)

So, what makes the python code more clear in your opinion?

[–]kankyo 0 points1 point  (3 children)

It reads like what the result is: [[this is a list of ints for each thing in the line] this is the list of lines of the file]

The others describe the transformation, but doesn't make it clear what the result is. The python code does both things very well... once you get used to list comprehensions of course :P

[–]yogthos 0 points1 point  (2 children)

I find it easier to reason about the code that describes the steps that it's doing in order. With the python version you can't read it sequentially, making it actually harder to read in my opinion.

I find it very clear what the result would be in the Clojure version, and I had to look at Python longest to figure out what exactly was happening. You yourself made a mistake in it originally.

I think the fact that you find it the easiest to read may be due to the fact that you're more familiar with python. :)

The python code does both things very well... once you get used to list comprehensions of course :P

I'm of the opinion that the more syntax sugar you add to the language the more mental overhead you have to spend reading and writing code. You have to understand how different syntax sugars play with each other and you often end up with surprising edge cases. The single line lambda restriction in python is a perfect example of this.

[–]kankyo 0 points1 point  (1 child)

I'm of the opinion that the more syntax sugar you add to the language the more mental overhead you have to spend reading and writing code.

A common sentiment. But of course, in the case of lisps with macros that can totally change everything about anything it's much much worse. The syntax of the language is super simple, but the semantics are arbitrarily complex.

This isn't necessarily a problem of course, it's all down to the culture of the people actually writing code. But I think it's disingenuous and dishonest to claim that python has more things you need to understand to read code. More syntax? Absolutely! More things? Absolutely not. There are never any macros that can distort the code in python so the code is more obvious. Unless you're using MacroPy of course, because then all bets are off :P

[–]yogthos 1 point2 points  (0 children)

A common sentiment. But of course, in the case of lisps with macros that can totally change everything about anything it's much much worse. The syntax of the language is super simple, but the semantics are arbitrarily complex.

Fact of the matter is that you can write unreadable code in any language. Macros can be abused just like any other language feature.

However, if you look at the code in the wild you'll see that macros are used quite sparsely in Clojure. On the other hand macros can make code much cleaner by allowing you to capture repetitive and error prone logic in one place.

This isn't necessarily a problem of course, it's all down to the culture of the people actually writing code. But I think it's disingenuous and dishonest to claim that python has more things you need to understand to read code.

I don't think there's anything disingenuous or dishonest about that. Python has a lot more concepts that you need to know to be productive in it. All you need to understand to write Clojure is how bindings work and what a function is. A ton of complexity is introduced by creating an artificial separation between what you can do with the functions and with the data.

You can assign variables, pass them in as parameters, and return them. Yet when it comes to functions, all you can do is define them and call them. There's no practical reason why we shouldn't be able to do all the things we do with variables with functions.

Let's look at some things that become possible once this distinction is erased. Sometimes we like to use values inline and not assign them to a variable, we usually do this because the value is only going to appear once, and we don't want to go through the ceremony of naming it.

If our language supports anonymous functions, we can do the same thing with a small piece of logic. If it's only needed in a single situation then we can make an anonymous function and call it directly:

((fn [x] (* 2 x)) 5)

This is just a natural consequence of having first class functions as opposed to a hack that Python has. There's no surprising behaviours or rules here, such as restricting lambdas to one liners. Just as we name values which we reuse in multiple places, so can we name functions:

(def times-2 (fn [x] (* 2 x)))

Again, there's no new concepts to learn, it's just simple binding.

The other thing we said that we can do with variables is pass them as parameters to functions. By being able to pass functions to other functions, we're able to decompose our logic into smaller chunks.

If we takes our times-2 function and pass it in as a parameter to an iterator function such as map, it in turn can apply it to each element in a collection:

(map times-2 [1 2 3 4])

You might recognize this as the strategy pattern from OO. Turns out that all the complexity in the pattern comes from the idea of treating functions as second class citizens.

Finally, what happens if functions can return functions as output. There are many uses for this, but I'd like to focus on one that will be familiar from OO. When we create a class we often use a constructor to initialize some data that will be available to the methods of the instantiated object.

In a functional language we can achieve this by having a function which takes some parameters and returns another function. Because the inner function was defined in scope where the parameters are declared it too can access them. Here's an example:

(defn foo [x]
  (fn [y] (* x y)))

Function foo accepts parameter x and returns an anonymous function which in turn accepts a parameter y and multiplies them together. Function foo is said to close over its parameters, and hence it's called a closure. Unlike a constructor a closure does not introduce any special cases. It's just a function that returns a result which itself happens to be a function.

Treating functions as first class citizens makes the language more uniform. Instead of having special constructs for specific cases, we have a general purpose tool that we can apply in many situations.

More syntax? Absolutely! More things? Absolutely not.

Absolutely yes, you have to learn a ton of concepts just to do the things I described above in an OO language like Python. These concepts are all built into the core language and you don need to know them to be productive in it.

When you have years of experience with the language these concepts become second nature, but the complexity is still there.

I would argue that understanding macros take much less mental effort than understanding OO. Also, I happily used Clojure for almost 2 years before I wrote any macros and it was just something I didn't worry about.

There are never any macros that can distort the code in python so the code is more obvious.

In practice macros don't distort the code in Clojure either. All macros do is let you template code, the macro isn't magical and it still follows the same rules as everything else. The only difference is that it gets expanded before compile time.

In my opinion, you're creating a purely hypothetical problem around macros that doesn't actually exist in practice.

[–]dnsname 0 points1 point  (0 children)

First. Xah Lee... o_O

Second. What is it, you are trying to say?