Live data from Hacker News

Just what does “code as data” mean anyway? (2014)

adambard.com

51–60 of 178 posts

Re: Just what does “code as data” mean anyway? (2014)

#51

See what I did there? Despite the good intention, no, because you're trying to explain lisp using lisp. If I knew lisp, I wouldnt need you to explain what code-as-data means. Maybe it's time for a different strategy. As a matter of fact, I've tried to explain code-as-data using OOP just this morning: https://news.ycombinator.com/item?id=16389514

Your linked comment is great but it sweeps one thing under the rug. There is a reason lisp didn't win. We put up with it (I use emacs, so I have to write it at times) but in general it's harder to reason about transformable lists of code than it is about plain old objects and their methods. Almost every problem elegantly solved in lisp has a parallel elegant solution in Ruby (using blocks, dynamic method definition,…

> in general it's harder to reason about transformable lists of code than it is about plain old objects and their methods.

I would say it's more fundamental, simply a large majority find it difficult to interpret and reason about trees, rather than sequential operations.

Re: Just what does “code as data” mean anyway? (2014)

#52
post #30

Earlier quoted context omitted.

If by code you mean actual text (as opposed to macropy, which generates ASTs that get directly compiled), what does that open up? I've found code generation to be mostly annoying, unless it's just fixing up code written by humans. What's the point of generating a bunch of code, so that it can be re-parsed to be executed? Might as well skip the intermediate step. To me it seems like having the computer physically pres…

Can you please elaborate a bit more on what was such a nuisance when working with this method. I'm currently building something out that is using this technique to (hopefully) save lines in code, dynamic user form generation based on with permissions, etc...

I oversimplified a bit, but the usual flow I've experience was:

The programmer runs the generator, which outputs some file(s) with code, which the programmer proceeds to edit: some areas, removing others, etc. Then for some reason the input to the generator (in your case, the permissions) or the generator itself change, and so it has to be run again. Now the programmer is left with the task of having to merge all the changes made to the original files with the new generated output. Alternatively, the programmer says "to hell with that" and updates the code manually instead of using the generator, in which case the tool was useful exactly one time.

As PaulHoule correctly pointed out, though, there are many exceptions to this; generally, if the programmer has no reason to manually edit the files, then the problem is avoided. In some of those cases, though, there's no point in generating textual code, which will then be converted to some other format; you can output that format directly.

Re: Just what does “code as data” mean anyway? (2014)

#53
I think there’s a much simpler example of how code is data:

    (hello world)
That’s a list containing two symbols. So it’s data. However, if I evaluate that list, it will call the function “hello” with the argument “world”. So it’s also code.

Incidentally, lists and function calls are identical in lisp, hence code is data.

I think the article just complicates it by introducing macros.

Re: Just what does “code as data” mean anyway? (2014)

#54
post #8

When I was first learning C, I wondered the point of macros; especially with inline functions. It wasn't until I taught myself Racket and Clojure a few years later that I realized the utility, and it was immediately one of those "the world is different now" moments; I could actually augment the language without having to contribute to the core compiler.

I'm still wrapping my head around macros, but I have been able to come up with a couple use cases that I quickly realized were already covered by existing macros, the most recent one being a less flexible -> macro. Anyways, I kept reading places that lisp macros and c macros were completely distinct, which seems untrue after a couple years of trying clojure.

>Anyways, I kept reading places that lisp macros and c macros were completely distinct, which seems untrue after a couple years of trying clojure.

I haven't used Clojure macros but as a programmer that uses both C and Common Lisp, I can say with good authority that C macros and CL macros are very different.

Re: Just what does “code as data” mean anyway? (2014)

#55
post #41

Earlier quoted context omitted.

I agree, OO is ultimately good enough. Problem is, coding using OOP is like printing ideas on paper: you dont wanna do that because it's hard to discard ideas that's been printed on paper. You wanna use post-its instead because post-its are easy to discard. Refactoring is hard using OO and refactoring is the key difference between waterfall and good software development. Refactoring is easier, even fun, using functio…

Refactoring lisp gets harder the more complex the program gets. Approachs that are clever and reasonable for a 1000 line program can become hell in a 100,000 line program. Effectively programming languages get less powerful the longer the program becomes. Breaking up programs into more powerful little pieces is never a clean separation and you need to make real trade offs between more separation and more power.

> Refactoring lisp gets harder the more complex the program gets.

I have found that to be true in dynamically or duck typed languages.

I have found it to be not true in strongly typed languages with good tooling. Example: refactoring large java program in, say, Eclipse.

> Breaking up programs into more powerful little pieces is never a clean separation

I would tend to disagree, to some extent.

In Lisp(s), there are lots of small, sometimes obviously correct functions that do powerful operations on abstract data structures. Part of the reason for this is that unlike OOP (and I write Java daily) which has lots of data structures, Lisp has very few. In OOP you model your domain with new classes. In Lisp you model your domain with combinations of a few basic structures. (In Clojure...) List, Set, Array, Map.

Maybe you have layers of separation. With one layer being between primitives that manipulate your domain objects, and another layer being the logic that decides how and when to do the manipulations. I want to solve a puzzle. The puzzle board has various states, pieces, and operations that transform a board into a new board state. Then the logic layer is an algorithm or algorithms written using the layer of primitive manipulations. That logic layer may, in fact, use canned algorithms, because the data structures are common primitives. Say, sort, search, A* search, depth first, breadth first, reduce, map fn over a sequence, etc.

You can end up with a large program. But it can be a lot easier to reason about than, say, a large Java program. It seems to take more deliberate effort to keep a large java program easy to reason about.

Re: Just what does “code as data” mean anyway? (2014)

#56

See what I did there? Despite the good intention, no, because you're trying to explain lisp using lisp. If I knew lisp, I wouldnt need you to explain what code-as-data means. Maybe it's time for a different strategy. As a matter of fact, I've tried to explain code-as-data using OOP just this morning: https://news.ycombinator.com/item?id=16389514

Your linked comment is great but it sweeps one thing under the rug. There is a reason lisp didn't win. We put up with it (I use emacs, so I have to write it at times) but in general it's harder to reason about transformable lists of code than it is about plain old objects and their methods. Almost every problem elegantly solved in lisp has a parallel elegant solution in Ruby (using blocks, dynamic method definition,…

> has a parallel elegant solution in Ruby (using blocks, dynamic method definition, or otherwise)

> Sometimes with lisp things get "spooky" in a way that just doesn't happen in practice with Ruby

Over here the long run practical experience with Ruby on a couple or three significantly involved codebases and assorted dependencies taught me over the years that such solutions are elegant for a month then end up being a hell to maintain, debug, and trace through. We definitely encountered my lot of "spooky" things in Ruby-land. For maintainable code on the long run we now vastly favour boring, non-dynamic, explicit, down-to-earth code. Dynamic solutions, DSLs or dependencies making use of those now therefore have to sustain solid justifications and heavy scrutiny before being greenlighted.

Re: Just what does “code as data” mean anyway? (2014)

#57

Does anyone have some examples of great products that have been built largely by leveraging lisp macros? emacs?

>Does anyone have some examples of great products that have been built largely by leveraging lisp macros?

Common Lisp itself.

The Common Lisp language is not only made up by data types, functions and control constructs: Many of the typical keywords a Common Lisp programmer would use, like defun (for defining a function), are macros themselves.

Many control constructs are macros as well.

So a good part of the language is built using macros as well.

The compiler itself (compiling Lisp to machine language) is also mostly made up of macros in many Lisp implementations.

Thus, the answer is: Common Lisp implementations are built largely by leveraging lisp macros. And i'd say those are "great products."

Re: Just what does “code as data” mean anyway? (2014)

#58

Would it be possible to do the same thing in Haskell using partial function application, where the first argument is the name of the tag and the second argument makes the " " tags and applies string concatenation?

Absolutely. However, I will note that, while Haskell makes partial application very easy, there's also nothing Haskell-specific about it: you can write curried functions in many languages, possibly by using helper functions or just manually transforming your code:

    def tag(tag):
        def render(*body):
            return '{body}'.format(
                tag=tag, body=''.join(body))
        return render

    html = tag('html')
    body = tag('body')
    h1 = tag('h1')
    p = tag('p')
I also should note that, while the macros in this case could be obviated by approaching the problem slightly differently, I don't think that's true of Lisp macros in general. I don't want to be be too harsh to this particular blog post—coming up with a two-or-three line motivating example that's accessible to a general audience without being contrived is very hard! But one consequence of the slightly-contrived nature of the example in this post is that it's very easy to come up with non-macro ways of solving the same problem, as demonstrated above.

Re: Just what does “code as data” mean anyway? (2014)

#59

One of the failures of conventional programming libraries is that parsing libraries such as yacc work in only one direction. In 2018 we could easily have libraries that work both ways by default, but we don't. Bidirectional parsing is great for code generation and opens up a lot of things you could do easily, but because common parsing libraries are unidirectional, people aren't aware of what you can do and don't cla…

>It is very possible and practical to parse conventional languages down to an AST tree, work on the tree, and run that code. Yes, of course. >Sometimes I wonder if the LISP cult is just trying to pretend Noam Chomsky was never born. Life is great at our cult, you see? Now, seriously, what you propose is to transforme "conventional language" (say, Java) to AST, work on it, and then spit out conventional language again…

In Lisp, my understanding is that while you can manipulate the AST directly, you will likely introduce a bug unless you use the special functions for handling hygienic macros? And each flavor of Lisp has its own way of doing hygienic macros.

Re: Just what does “code as data” mean anyway? (2014)

#60
post #8

When I was first learning C, I wondered the point of macros; especially with inline functions. It wasn't until I taught myself Racket and Clojure a few years later that I realized the utility, and it was immediately one of those "the world is different now" moments; I could actually augment the language without having to contribute to the core compiler.

I'm still wrapping my head around macros, but I have been able to come up with a couple use cases that I quickly realized were already covered by existing macros, the most recent one being a less flexible -> macro. Anyways, I kept reading places that lisp macros and c macros were completely distinct, which seems untrue after a couple years of trying clojure.

> I kept reading places that lisp macros and c macros were completely distinct, which seems untrue after a couple years of trying clojure.

They're similar in that they're both compile-time functions that generate code, but they're very different in practice, because C macros are written in a text substitution language that knows virtually nothing about C, which naturally makes writing even simple macros very error-prone. Lisp macros, on the other hand, are written in plain old Lisp and receive regular Lisp data structures that you can use the whole language to operate on.

A lot of people learn from C that macros are very difficult to get right, so it's important when they move to Lisp that they give them a second look, because Lisp is a much more capable language for code transformation than cpp.

Post reply on HN