Hacker News new | past | comments | ask | show | jobs | submit | tuveson's comments login

How would you write this in Lisp without introducing a bunch of parens? `if (0 <= x + m && x + m <= n) {…}`

I’m not a Lisp hater, but there’s a reason people make this criticism. I think most people find the infix version easier to read.


    (if (<= 0 (+ x m) n) ...)
There is one additional set of parentheses due to the simple addition, which I already mentioned.

I unwittingly chose an example that allowed you to write fewer s-expressions in Lisp… touché.

I've been working on this for TXR Lisp. The next, release 300, will support infix.

The core expression in your example expression will parse as you have written it. Except we have to add ifx:

  1> (ifx (if (0 <= x + m && x + m <= n) (prinl 'do-this)))
  ** expr-1:1: warning: unbound variable x
  ** expr-1:1: warning: unbound variable m
  ** expr-1:1: warning: unbound variable x
  ** expr-1:1: warning: unbound variable m
  ** expr-1:1: warning: unbound variable n
  ** expr-1:1: unbound variable x
Though it doesn't work since it's not a complete example with defined variables, we can quote it and expand it to see what the expansion looks like, which answers your question of how you write it one underlying Lisp:

  1> (expand '(ifx (if (0 <= x + m && x + m <= n) (prinl 'do-this))))
  (if (and (<= 0 (+ x m))
        (<= (+ x m) n))
    (prinl 'do-this))
The ifx macro deosn't have to be used for every expression. Inside ifx, infix expressions are detected at any nesting depth. You can put it around an entire file:

  (ifx
    (defstruct user ()
      id name)

    (defun add (x y) (x + y))

    ...)
Autodetection of infix add some complexity and overhead to the code walking process that expands macros (not to mention that it's newly introduced) so we wouldn't want to have it globally enabled everywhere, all the time.

It's a compromise; infix syntax in the context of Lisp is just something we can support for a specific benefit in specific use case scenarios. It's mainly for our colleagues who find it a barrier not to be able to use infix.

In this particular infix implementation, a full infix expression not contained inside another infix expression is still parenthesized (because it is a compound form, represented as a list). You can see from the following large exmaple that it still looks like LIsp; it is a compromise.

I took an example FFT routine from the book Numerical Recipes in C and transliterated it, to get a feel for what it's like to write a realistic numerical routine that contains imperative programming "warts" like using assignments to initialize variables and such:

  (defun fft (data nn isign)
    (ifx
      (let (n nmax m j istep i
            wtemp wpr wpi wr wi theta
            tempr tempi)
        (n := nn << 1)
        (j := 1)
        (for ((i 1)) ((i < n)) ((i += 2))
          (when (j > i)
            (swap (data[j]) (data[i]))
            (swap (data[j + 1]) (data[i + 1])))
          (m := nn)
          (while (m >= 2 && j > m)
            (j -= m)
            (m >>= 1))
          (j += m))
        (nmax := 2)
        (while (n > nmax)
          (istep := nmax << 1)
          (theta := isign * ((2 * %pi%) / nmax))
          (wtemp := sin 0.5 * theta)
          (wpr := - 2.0 * wtemp * wtemp)
          (wpi := sin theta)
          (wr := 1.0)
          (wi := 0.0)
          (for ((m 1)) ((m < nmax)) ((m += 2))
            (for ((i m)) ((i <= n)) ((i += istep))
              (j := i + nmax)
              (tempr := wr * data[j] - wi * data[j + 1])
              (tempi := wr * data[j + 1] + wi * data[j])
              (data[j] := data[i] - tempr)
              (data[j + 1] := data[i + 1] - tempi)
              (data[i] += tempr)
              (data[i + 1] += tempi))
            (wr := (wtemp := wr) * wpr - wi * wpi + wr)
            (wi := wi * wpr + wtemp * wpi + wi))
          (nmax := istep)))))
It was very easy to transliterate the C code into the above, because of the infix. The remaining outer parentheses are trivial.

A smaller example is a quadratic roots calculation, from the test suite. This one has the ifx outside the defun:

  (ifx
    (defun quadratic-roots (a b c)
      (let ((d (sqrt b * b - 4 * a * c)))
        (list ((- b + d) / 2 * a)
              ((- b - d) / 2 * a)))))
sqrt is a function, but treated as a prefix operator with a low precedence so parentheses are not required.

wot if ya mum ran on batteries

Why? It's a static site generator. He controls the inputs - there's basically no attack surface. I can't think of a situation where lack of memory safety would be less of a problem.


> Also, it's definitely not nearly as easy to implement

I think this is the real reason why there are so many dynamic language implementations. If you want to implement a dynamic language, you just slap a type tag on your runtime objects and boom, your "type system" is done.

Dynamic languages get a lot of expressiveness "for free", whereas having a really expressive static type system requires a lot of work. It's not that hard to get a type system on the level of C, but if the language is interpreted, it's still going to be pretty slow.

I do think there can be benefits to having typing in a scripting language (and not a bolted-on type system like typescript or mypy). It's much easier to work with an FFI if the type system of the scripting language maps closely to the implementation language. It also does make it much easier to optimize the language down the line, if that becomes a priority. Making a fully dynamic language efficient is very, very difficult.


llama.rs, of course /s


> When I read the article it was very clear, due to the compiler's in-memory graphs, that they needed a GC.

It's actually pretty easy to do something like this with C, just using something like an arena allocator, or honestly, leaking memory. I actually wrote a little allocator yesterday that just dumps memory into a linkedlist, it's not very complicated: http://github.com/danieltuveson/dsalloc/

You allocate wherever you want, and when you're done with the big messy memory graph, you throw it all out at once.

There are obviously a lot of other reasons to choose go over C, though (easier to learn, nicer tooling, memory safety, etc).


I get the impression they'd use smart pointers (C++) or Rc/Arc (Rust)


> untyped references to strongly typed values

I would call that weakly typed since there is no step before your code runs that validates (or even tries to validate) that your program satisfies a "type system". But since the definition of "strong" and "weak" typing has always been vague, I will just say that it is stupidly typed[1], and that I am a pedantic nerd.

1. https://danieltuveson.github.io/programming/languages/stupid...


For parity, C should add a `prefer` keyword that hoists statements to the top of the function.


You have top and bottom, but what about hoisting sideways?


Sounds like came_from might be helpful in implementing prefer?


That seems actually feasible, with instruction scheduling. But would probably ruin a lot of the memory alignment optimisations.


No, C is not perl. They do have BEGIN blocks, but these are constexpr.


Maybe surprising to hear white people saying it, but most black people (or people who grew up in predominantly black neighborhoods) throughout the US have been using it for a long time. I would assume that's a byproduct of The Great Migration.


Step 1: write an RFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFC

Step 2: write an RFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFRFC

…and so on


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

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

Search: