How does lisp compare to Ruby/Elixir?

I’ve used vim for many years, and recently, thanks to the ‘what editor do you use for elixir’ thread, tried out Spacemacs.

The Spacemacs config file is all emacs lisp, which is bizarre but I’m trying to learn it.

Now, I’ve known about the cult of lisp users and evangelists, and how its the “bar” that every other language is compared against.

I don’t see myself writing anything in this language, but I wanted to learn it anyway to see why it’s so “enlightening.” I have heard learning lisp makes you think about code differently that no other language can offer.

All the lisp/elsip guides I’m reading, however, are extremely confusing, and my brain kind of just thinks…why…why is all this …like THIS

Why so many parentheses or confusing syntax. Obviously there’s some magic here that everyone must see, but I think the people that see it are people who mainly write in more static typed or lower level languages like C/Java, etc.

I’m trying to find a “why use lisp” comparison between lisp and ruby/elixir, the languages I have the most experience in. Is that even a fair comparison?

It seems the main benefit is “its easier”, according to Practical Common Lisp: http://www.gigamonkeys.com/book/introduction-why-lisp.html

But… how does it get any easier than writing ruby, I literally couldnt imagine it getting easier (syntax wise). Semantic wise, I also love Elixir for its functional style, which is also easy on the brain. Lisp doesnt seem easy on the brain at all, I gave up after 30 minutes of trying to even do a basic loop over a list (I started out trying to do fizzbuzz)

2 Likes

Elisp isn’t the best representation of LISP.

Easy … Simple made Easy

Why so many parentheses or confusing syntax.

i.e. only because it feels unfamiliar to you because you learned a different way first.

Not all languages make it necessarily “easy” to learn the next language - in extreme cases this happens.

For an introduction to LISP/Scheme I recommend Racket.

Quick: An Introduction to Racket with Pictures

9 Likes

Hmm very interesting, thank you. I’ll read through all this.

What is the difference (very simply) between:

  • Common lisp
  • Emacs lisp
  • Racket
  • Scheme

So far the “Why Racket?” List of 10 things is by far the best explanation I’ve ever read about why Lisp is so cool. I’m only through a few of them and this already makes way more sense.

1 Like

Why does Elisp suck

Scheme or Lisp? Kent M Pitman explains the deep philosophical differences.

Racket used to be called PLT Scheme and within DrRacket is used to implement five separate teaching languages. It has immutable and mutable data types, supports gradual typing, macros, and class-based objects and has a number of available libraries.

See also:

YouTube: Types are like the Weather, Type Systems are like Weathermen - Matthias Felleisen.

2 Likes

Something that I appreciate, personally, about Lisps is that they mostly have a lack of arbitrary rules and regulations.

One example of this is naming: A function/variable can use an immense set of characters because we don’t have to care about things like operators being parsed in any special kind of way in a Lisp.

#lang racket/base

(struct player (name ready?)
  #:transparent)

(define (player-list->ready% players)
  (define (filter&count predicate lst)
    (length (filter predicate lst)))

  (define ready-players-count (filter&count player-ready? players))

  (define (fraction->% fraction)
    (exact->inexact (* fraction 100)))

  (fraction->% (/ ready-players-count
                  (length players))))

Syntax highlighted version @ http://pasterack.org/pastes/87251

In that short snippet you’ll find several things that aren’t necessarily hard to find one by one in languages, but they exist in Racket (and most of them in all Lisps):

  • Function names and variables aren’t as limited by what needs to be parsed as an operator. We can’t use list or quote delimiters in function/variable names and that’s about it.

  • Functions can be defined in inner scopes and so they can be found easier when sub-dividing a problem in different functions, all while safely scoping things so that we can even use outer variables in the inner functions without even passing them.

  • Higher order function passing and application doesn’t need special operators, because if a variable is bound to a damn function, why should we need ceremony to use it as such?

This is just a 15 line snippet and I’m not going to claim it shows a meaningful part of what makes Lisps great to me. I will say, though, I think it shows the spirit of Lisp: You’re free to do what you want, how you want to do it and a Lisp (and especially any descendant of Scheme) will have such a simple set of rules and consistent behavior that you’ll know how it works. You’re the programmer, Lisp is just your vehicle.

6 Likes

They all come from the same family of languages but they differ in some details. For example the syntaxes are very similar and once you “get” one of them the others just “are”. The basic syntax IS very simple and very consistent, everything has the same structure. Lists for doing things (do-what arg-1 arg-2 ...) and literals, and that’s it. Look at @gon782 example code. And of course lisp macros leave all other for dead.

There are some lisps running on top of Erlang/OTP with my favourite LFE (Lisp Flavored Erlang) https://github.com/rvirding/lfe . Yes, it was written by me, but I am not biased.

5 Likes

Awesome, thanks for all the tips guys.

I’m learning with Dr. Racket right now. I have no idea why it’s so confusing. I stare at the most simple example, like (+ 2 5) and it makes perfect sense. It’s also easy to understand nesting things like (+ 2 (- 6 4)). Where it gets weird is like… what actually is happening when youre nesting multiple function definitions… or how the return values of some function fits into the definition of another. I stare at it for 10 mins just waiting for it to click.

I’ll get there though, its quite fun to think in such a different way. It’s like stretching the brain.

Take this for example:

(define (square n)
 (filled-rectangle n n)

This is the same as…

def square(n) do
  filled_rectangle(n, n)
end

All that makes sense when I look at it as a ‘whole’, but when I zoom in on the actual parts is where it gets confusing again.

So its my understanding the syntax of lisp is

(func arg1 arg2)

So that means define is taking (square n) as argument 1, and (filled-rectangle n n) as argument 2.

For argument 2, that makes sense because filled-rectangle is an existing function, and its calling it with 2 arguments just like (+ 2 3). So that means the return value of THAT is what that particular function you’re defining returns.

But how the heck does (square n) work…

How does this even work if square is something you’re creating/defining (the name of the function). Wouldn’t this actually try to call a function called square and pass in a variable n that doesnt exist?

Actualy this:

(func arg1 arg2)

is function call, and not definition.

So…

def square(n) do
  filled_rectangle(n, n)
end

translate to this:

(define (square n)
 (filled-rectangle n n))

and this

function_name(arg1, arg2)

translate to this:

(function_name arg1, arg2)

I think that you may have a hard time, when you mix mentally function definitions, and function calls (well I did have initially) but when it starts to click and you instantly, at glance can differentiate definitions from calls… you can probably will understand it much easily :slight_smile:

And probably you are right, define takes list of (function_name, arg1 ... argN) as first argument and (actual function body) as second argument. And that function body can consist of many, many other functions and use all the arg1 to argN arguments inside. When you look at this… its’ f***ing brilliant :smiley: Anyway, after all define is taking name, ars, and other functions, to produce another function that takes those args, and returns what those other functions would do.

Just one last edit, when writing this response something started to click for me too, and/// whoa, why did postponed lisp for so long? Lisp makes so much sense and is really brilliant in it’s simplicity.

2 Likes

And that is a list - the head of the list is the function position (that is why in Elisp a list of data starts with '( instead of () while the remainder of the list are the arguments.

I have no idea why it’s so confusing.

Because of “math class” you expect expressions to look like:

g(x) = f(x) + c

and imperative programming languages trained you to expect statement blocks

int main() {
   printf("Hello, World!");
   return 0;
}

You need to give your brain some time to calibrate your internal text pattern-match to properly destructure LISP s-expressions (and do yourself a favour and use an editor with strong parens matching capabilities; what am I saying, you are trying out Spacemacs) - once that happens many familiar programming languages look downright crude (then you have to learn Erlang because Erlangers know that syntax is irrelevant).

That reminds me; I still have to give Speaking Data: Simple, Functional Programming with Clojure a spin.

4 Likes
(define (function-name args ...)
  body
  ...)

is a special form of the define macro that expands to

(define function-name
  (lambda (args ...)
    body
    ...))

That is to say it binds the variable function-name to a lambda (function) that takes args ... as arguments and executes body ..., where body can be any number of expressions.

(define (square n)
  (filled-rectangle n n))

expands to

(define square
  (lambda (n)
    (filled-rectangle n n)))

square is then a function taking one argument n and returning the application of (filled-rectangle n n).

4 Likes

I wish I understood the first part of what you meant more, but I haven’t gotten to the part on lists yet in the Dr Racket tutorial. I’m still confused about how…

(define (square n)
 (filled-rectangle n n))

…creates a function with the name square and argument n instead of attempting to evaluate an existing function called square and passing in a variable n.

Ooooh… that explains it. I’ll need to go over that a few times.

2 Likes

define is a macro, which means that it doesn’t execute its arguments, but rather takes them as quoted forms. In Racket it’s actually a syntax object. It’ll then transform that syntax object and expand it to other code. This is the basis of how the language is built up and it’s available for you to use later on when you’re feeling comfortable with the language and can move on to making macros.

3 Likes

Damn ok, I get it at a high level, thanks for your great explanation above.

(define square
  (lambda (n)
    (filled-rectangle n n)))

When I see it in this format it makes more sense, because that’s passing square in as a “string” (right?). And then the body of that is a lambda function.

Another part I’m confused on is, what are all the data types of lisp (or rather, Racket).

Is it just… nil, t, string, list?

Sorry it’s a Clojure thing:

Invoke a function with the name of the function in “function position” (the first element of a list)

It just emphasizes how simple (and composable) the building blocks actually are - the whole “Code is Data. Data is Code” thing.

3 Likes

square is a symbol here, yes. It can be written as 'square or with (quote square) if you’re in the REPL. It’s for all intents and purposes an atom from Elixir. What the define macro will do is take the literal value of that symbol and not actually a bound value.

One of my first macros in Racket just to explore different language features was to make a macro that worked like this:

(py-for n in (range 5)
  (printf "~a~n" n))

When I’m passing in the symbol in in this example I’m not actually referring to a bound variable and in the definition of this macro I’m in fact ignoring that part of the passed in arguments entirely. It’s only if we choose to transform this passed in value to an actual binding that the compiler will complain about unbound values.

3 Likes

Also, for fun, check how def is implemented in Elixir. It’s not too different from how Lisp does it, and the “problem” of defining/mentioning names and using names is quite similar.

I’m not going to advice you to read Goedel, Escher, Bach (ok, I will, but I’m not sure it’ll help ;-)). Maybe http://cs.lmu.edu/~ray/notes/usemention/? The basic thing is the same and I guess it’s important to have a good feeling for the differences especially when diving into macro-land (in either Lisp or Elixir).

I think Elixir and Lisp are quite similar, with one big distinction: homoiconicity. What makes Lisp unique is not necessarily the prefix notation (and you’ll quickly get over that anyways) or the parenthesis (ditto, it’s not bigger than switching from curly-bracket-space like C to defined whitespace like Python) but the fact that code and data have the same form. It’s essentially all data, just some data gets executed. Given that data manipulation is (or at least should be) simple in any decent programming language, code manipulation is simple in Lisp. That makes building things like very powerful internal DSLs a lot easier than in other languages, with the upside of enormous increases in productivity (and the downside of discovering that you now wrote a different language - pretty much the reason that people in both the Lisp and Elixir worlds don’t leap into writing code manipulating code too quickly).

2 Likes

So macros aside (I get to that soon in this tutorial, not trying to get too far ahead without understanding the basics), can you explain again what ‘quoting’ is again?

Like how is (square 10) different from '(square 10)

All I know is that it doesn’t evaluate it, and seems to just turn it into a symbol or something… like this is similar in elixir to: :"(square 10)"

But… how is that useful, what do I use it for?

And lastly – how is a symbol above different from a string "(square 10)" in racket

Edit: Also lastly lastly – why is this in square brackets?

  (let ([p12 (hc-append p1 p2)]
        [p21 (hc-append p2 p1)])
    (vc-append p12 p21)))