Hacker News new | past | comments | ask | show | jobs | submit login

I don't get it why Clojure and other Lisp tutorials throw things like this in face of beginners:

    (reduce + (map (comp inc inc) (range 10)))
instead of just using things like the "->" macro to make the damn thing just as in any other language:

    (-> (range 10)
        (map #(+ % 2))
        (sum))
...it's as if they purposely try to scare people away from Lisp! Some kind of smug superiority makes them wanna make sure no "lesser mortals" want to touch their beloved language. (The added benefit would be that later you can "blow their minds" but showing them how to write "->" themselves and instantly introduce them to the power of macros!)

This is almost close to showing a code snippet like this to someone trying to learn Haskell:

    fix$(<$>)<$>(:)<*>((<$>((:[{- thor's mother -}])<$>))(=<<)<$>(*)<$>(*2))$1
(it only prints the powers of 2, but it was enough to scare some people into calling Haskell “the Taliban version of ML”... http://www.theregister.co.uk/Print/2012/12/21/financial_soft...)

...really, people, stop scaring learners away!




Because -> is not a typical functional idiom, it's a macro specific to Clojure, and anything you can do with -> you can also do in the standard functional way, and beginners are better served by understanding the way a lisp works rather than only get taught syntactic sugars. Keep in mind that -> as a macro does not evaluate its arguments the way a function does, which complicates the understanding for a beginner tremendously, since Clojure is actually an eagerly-evaluated language.


Ha, yeah, I get where you're coming from. And I toyed with putting a threading macro example inline, and in the end put a footnote in instead.

My reasoning was that basically, regardless of the threading macros, you are going to have to learn to read/write s-expressions as-is for a couple of reasons:

1. The threading macros won't be the best way forward in all cases. 2. You're going to need to read other people's code, which wasn't using the macros.

Overall, I was trying to make the point that it is a real hurdle (at least that's how it felt for me), but worth getting over. As a week or so later, you can read s-expressions with relative ease. That it's worth making the effort, because they are pretty awesome. Maybe I just didn't communicate that very well.


Good point. My code is littered with threading macros all over the place, and it can really reduce ))))))))))) to ). But I wouldn't have been able to use it effectively if I didn't know how s-expressions work.


This is subjective and dependent on the background. I, as a beginner, found the original expression easier to understand, whereas, the "->" macro rewrite seems puzzling. Specially (map #(+ % 2)) looks as if the values are summed and then % 2 is applies to it.


For an introduction to absolute beginners it could be something like:

  (->> (range 10)
    (map (fn [x] (+ x 2)))
    (reduce +))
And then later on you can explain the #() shorthand


As a Clojure/Lisp beginner, I can't say I agree. I don't know any language that works like your example, except maybe shell script with its pipes. Without variables, that expression just seems too "magical". Nested function calls, on the other hand, are common everywhere.


If it's particularly confusing you can always use swiss arrows[0] instead:

   (-<> (range 10)
        (map #(+ % 2) <>)
        (reduce + <>))

[0] https://github.com/rplevy/swiss-arrows

It's more common in functional languages and called a thrush or pipe operator.

F#:

    [|0..9|] |> Seq.map ((+) 2) |> Seq.sum
Javascript (Ramda):

    R.pipe(R.range(0,10), R.map(R.add(2)), R.sum)();

    //With slightly more magic
    let {pipe, range, map, add, sum} = R;
    pipe(range(0), map(add(2)), sum)(10);


While they might make the flow clearer, I don't think the swiss arrows fix what I perceive as more confusing for a newcomer.

The problem is that -<> looks like a regular function call, but does things that a function call can't do. In a language without macros, which are most of the mainstream ones, <> would have to be a language feature, and then it's not clear why you'd need to use -<> to 'activate' it.

Now, I've read enough about Lisp and AST and such to understand what a macro is, but the concept doesn't map easily to mainstream languages.


Clojure's standard library already has a threaded placeholder:

    (as-> (range 10) _
      (map #(+ % 2) _)
      (reduce + _))


JavaScript (lodash-fp):

    _.flow(_.range(0,10), _.map(_.add(2)), _.sum)();

    //With slightly more magic
    let {flow, range, map, add, sum} = _;
    flow(range(0), map(add(2)), sum)(10);


...that's how chaining method calls end up working in most OO languages (take the Scala example in OP's article). And also in Javascript (I can't remember if there is something like `range` in js right now...):

    [1,2,3,4,5,6,7,8,9,10]
      .map(function (x) { return x + 2 })
      .reduce(function (a, b) { return a + b })
so the "." in OOP ends up working like "->" or "->>". Heck, you can make the dot in OOP code behave like anything, even like monadic bind (`>>=` or `do` notation in Haskell), if you do OOP-style-monads in JS: https://www.youtube.com/watch?v=b0EF0VTs9Dc

(off-topic: this is also my main problem with OOP... the "." can actually end up meaning anything practically, as program logic is concerned, it's like some kind of "infinite operator overloading" :) )


But (conceptually) the "." in most OO languages is something that is part of the object it's being applied to, while the -> is something external that one applies to the subsequent expressions. At least, this is what I perceive by looking at the code, and in my opinion, the two concepts don't map intuitively.


No (at least not in one specific OO language, namely C++).

"foo->" means exactly the same thing as "(*foo)." That is, you use "." when you have an object, and "->" when you have a pointer to an object.

I don't know of any other OO language that uses "->". If there is one, my comments here probably do not apply to it...

(Or were you talking about the "." as used in OO, compared with the "->" as used in Clojure? If so, disregard this comment...)


(Or were you talking about the "." as used in OO, compared with the "->" as used in Clojure? If so, disregard this comment...)

I'm afraid I was :)


Why other Lisp tutorials don't teach -> is, firstly, that other Lisp's don't have that, except as some add-on library feature. Secondly, it's a syntactic sugar that has quite a bit going on under the hood: implicit parameters, a lambda function being expressed as #(+ % 2) with this % thingy, and return values of forms being passed as arguments to other forms that are not syntactically enclosed. You have to understand that (range 10) is just called normally, but then its return value becomes an extra invisible argument to the (map ...) form.

Newcomers should learn the underlying ordinary evaluation first.


Totally agree with your point, but just a quick note: using the thread-first -> macro won't work in this case, instead you'd use the thread-last macro ->>. Also Clojure doesn't have a sum function, so you'd actually use (reduce +).


only in this case, it should be ->>, because -> inserts argument into first position, but you need it in the last... And you can omit parentheses around functions without additional arguments (sum in this case)


First principles I guess, although I sure love `->` and its siblings.


If you start thinking along the lines of first principles you could just as well start with a NAND gate.

..because some people were even smart enough to pull this off: http://www.nand2tetris.org/ :)


McCarthy had something to say about this... http://www-formal.stanford.edu/jmc/recursive.pdf


Which paper is the one with the symbolic differentiator ?


Recursive first principles. I don't think n2t will put the emphasis on that (no offense, I love their work and results, something I wished for years).


EDIT: as observed by others, `->` should actually be `->>`, sorry for the typo, but unfortunately I can't edit the original comment anymore. Also forgot there's no "sum" already defined in Clojure, so replace that with `(reduce +)`


I'm new to Clojure and I'm trying to parse this line:

  (reduce + (map (comp inc inc) (range 10)))
Is the following a correct Haskell equivalent?

  foldr1 (+) (map (succ.succ) [1..10])


Mostly, but I'm pretty sure Clojure's range gives the integers from 0 to 9.


pretty much almost all folds in strict languages can be assumed to be left folds.


Yep!




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: