Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Lisp is a fun language to program in. I learned Lisp after already being familiar with Java / Python and some other languages, which maybe made it even more beautiful.

One of my favourite pieces of code is a Lisp REPL in Lisp:

    (defun repl()
            (loop (print (eval (read)))))


looks even better with threading in imaginary lisp dialect:

    (-> read eval print loop)


    (defmacro -> (&rest args)
      (loop for item in (reverse args)
            for result = (list item) then (list item result)
            finally (return result)))


No longer imaginary. Power of LISP right there.


Agree to disagree; to write lisp one must know how to read it.

The threading macro is great, but the GP one-liner tells me more about lisp.


Lisp is all about being able to compile the latter to the former in compile-time, if you want it. That's the major problem lisp solves.


Hard agree!

Still one is the latter and the other, the former. Hence I think the first tells more of the lisp story, with just a few more parens. ;-)

"Why is it called REPL when the code says LPER? Well..."


Why is it called "root mean square" when you square first, then take the mean, and then the root?


We can just cut straight to “why is function composition backwards?”


It's not backwards. The text is linear, but the code isn't. It's outer most nested function to the innermost nested function.

Makes most sense if you think of HTML

<html> <body> <div> </div> </body> </html>

would compose as

compose( html, body, div )


`(root (mean (square x)))`

Is this more readable?

`x ~> square . mean . root`

Where `~>` "sends" a value to a function `x ~> f := f(x)` and `.` is the "backwards" function composition `f . g := \x (g (f x))

I think I prefer the "backwards" notation ("root mean square"), but I can see the appeal of "square mean root"


I like having both notations at my disposal. For example, F#'s forward pipe operator[0], or pipes from a unix shell.

[0]https://docs.microsoft.com/en-us/dotnet/fsharp/language-refe...


I think clojure has that; I could be wrong, though, I've never used clojure.


Clojure does indeed have -> (thread-first), ->> (thread-last), some->, some->>, cond->, and as->. [0]

(->) inserts each form's value between the function name & the first argument in the next form.

(->>) inserts each form's value as the last argument in the next form.

The latter forms are much more niche and I haven't found need for them yet.

[0]: https://clojure.org/guides/threading_macros


Bucklescript/ReasonML have that too:

CLJ:(->)/BS(|.)/RML(->) called Fast Pipe.

CLJ:(->>)/BS(|>)/RML(|>) called Pipe.

plus some nifty placeholders for other positions:

https://reasonml.github.io/docs/en/fast-pipe#pipe-placeholde...


as-> is great for when you have to mix thread-first and thread-last.

cond-> is great for building maps where some keys might not be needed:

    (cond-> {:k1 v1}
      v2 (assoc :k2 v2)
      ...)
some-> and some->> I don't use as often, but when mixing in some java code that could throw an NPE, these will avoid that and just return nil early (they short-circuit when getting a nil value).


You can next ->> inside ->, took far too many years for me to realize:

    @(-> ctx
        :hypercrud.browser/fiddle
        (reactive/cursor [:fiddle/ident])
        (->> (reactive/fmap name)))
another

    (-> [10 11] 
        (conj 12) 
        (as-> xs (map - xs [3 2 1])) 
        (reverse))


It does, sort of. As another commenter pointed out, -> chains the operands at the beginning, but also ‘loop’ form requires list of bindings.

It is totally possible to write custom -> macro that works correctly as used in my example.


I've always thought (probably because I'm not that smart) that REPL really should have been coined as REPR instead.

Though I guess that wouldn't be valid lisp if you tried to express it literally.


R as in repeat? REPL makes sense if you read it as "read-eval-print loop" (loop being the noun, not another verb).


R as in REPR


Python 3 for comparison:

    while True:
        print(eval(input(">>> ")))


Not really:

   ovov@ovov ~> cat repl.py
   while True:
       print(eval(input(">>> ")))
   ovov@ovov ~> python3 repl.py
   >>> x = 1
   Traceback (most recent call last):
     File "repl.py", line 2, in <module>
       print(eval(input(">>> ")))
     File "<string>", line 1
       x = 1
      ^
   SyntaxError: invalid syntax


`input` isn't READ, it's READ-LINE. READ reads one Lisp form, even if it's more than one line, and even if there are more than one on one line.

Python's `eval` also isn't sufficient here. For example, you cannot enter that loop you wrote at the prompt it provides when you run it, even if you write it on one line. You could use `exec`, but then everything would print as `None`.

There's the larger point about forms vs strings and printing readably and such, but this doesn't even provide the same basic functionality that a Python programmer would expect from a REPL.


This is a fundamentally different function that works with strings and not symbolic data structures.


That is cool, but it's pretty similar in Ruby.

    def repl
      loop { puts( eval gets ) }
    end
I guess I just prefer Ruby for general legibility. The same type of bracket everywhere makes pairing them mentally an error-prone chore. At least for yours truly.


This is actually a very different piece of code.

puts and gets are string functions. read and print are code deserializers and serializers.

eval is a function that takes a data structure representing code (as deserialized by read) and computes its value as a data structure, and serializes it.

While you might get the same effect as a user, the mechanics and metacircularity are lost.


plus, it's a gepl, not a repl.




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

Search: