Arc Forumnew | comments | leaders | submit | akkartik's commentslogin
1 point by akkartik 2009 days ago | link | parent | on: Observations on homoiconicity

You might enjoy this talk which mentions this equation:

    code = AST + formatting/comments
https://www.youtube.com/watch?v=2tCY72t1nL0

-----

2 points by Mitranim 2008 days ago | link

I might check it out later, but is there a gist in text form somewhere?

-----

1 point by akkartik 2010 days ago | link | parent | on: Observations on homoiconicity

Thanks for sharing! It's been a while since I thought about this, but my conclusion a few years ago[1][2] was that homoiconicity was an ill-posed idea. A language only really needs two properties to make macros convenient:

* Everything is an expression, and

* Unambiguous parens.

[1] http://arclanguage.org/item?id=16924

[2] http://arclanguage.org/item?id=19485

---

That said:

* I like symbols. Having symbols and strings in a Lisp doesn't seem any more weird than having variables and string literals in Algol or Java. Even if I _could_ use one in place of the other, it seems useful to separate symbols as part of the program from strings as part of the environment/domain/data model. You could implement one with the other, sure, but I wouldn't question anyone who chose not to. After all, every attempt I've seen to support spaces in symbols has been quite ugly.

* Your other points seem to be in the context of building tools like code formatters where the output will be seen by humans. That's not really the context for Lisp macros. I haven't really thought much about how useful they would be. Since I believe in macros, I can probably be persuaded. So that might be an interesting post to write. Why do you care about emitting exactly the code that was parsed? What new apps does it enable?

Are you aware of any languages that perfectly reproduce input layout? Even Go supports gofmt only by tightly constraining how programs "should" be indented, and only outputting that layout.

-----

2 points by Mitranim 2010 days ago | link

> Why do you care about emitting exactly the code that was parsed? What new apps does it enable?

Got a `gofmt` addiction, can't go back. Auto-formatting should ship with every language.

Briefly skimmed the Go implementation, and you seem to be right: it seems to lose whitespace and enforce its own formatting.

> Are you aware of any languages that perfectly reproduce input layout?

For now just my own. [1] The language isn't real yet, and might never be realized, but it has a base data notation (very Lisp-like), a parser, and I just started writing a formatter. Because the AST for the data notation preserves whitespace and comments, the formatter can print the code _exactly_ as is. This has interesting repercussions.

For a fully-implemented formatter for a fully-defined language, you wouldn't need whitespace; see Go. However, being able to print everything back means your formatter is usable from the start. It can support one or two simple rules, making only minor modifications, but you can use it on real code right away. Furthermore, this means we'll _always_ be able to choose which rules to enable or disable, which can be handy if the family of languages described in terms of this notation has different formatting preferences. I actually want the formatter shipped with the language, like `gofmt`, to be non-configurable, but this still seems like a useful quality.

[1] https://github.com/mitranim/sublime-mox

-----

2 points by Mitranim 2010 days ago | link

Should clarify something (follow-up to previous reply, see below or above).

Most macros don't want to deal with whitespace and comments. We also might want to _not let_ them, otherwise people will start using comments as code, like in Ruby. Macros just want expressions. So, we would define a second level of the AST and perform a second pass.

For the same base notation, there may be multiple languages defined in terms of it. If such a language has any form of prefix or infix, or uses the `outside_parens()` calling convention, the second pass would have to group nodes into expressions, in ways specific to that language. Furthermore, it should be addled with metadata about packages, types, and so on. The resulting AST is compiled, fed to macros, etc.

-----


New 2-minute video: defining functions in this postfix language.

https://archive.org/details/akkartik-2min-20201020

-----


That's a great point. As I've built this, the keystone example in my mind has been the `square` function:

    x square = x x *
So it's really funny that I haven't added it yet. Now added.

-----

2 points by zck 2019 days ago | link

Nice!

Is it that you're trying to reduce stack juggling? That's hard to understand from simple examples (that you need to have to grok the syntax). Maybe compare something like finding the hypotenuse of a right triangle given the two other sides. Without naming arguments, you'd have to do something like:

  hypot = dup * swap dup * + sqrt
But with named arguments:

  hypot x y = x x * y y * + sqrt
That seems a little easier to read, but even the original isn't so complicated. Is there an example that makes it more obvious why it's better? Even your `square` example is more tokens than with stack juggling (without arguments, `square = dup *` is just four tokens, compared to six with named arguments). I'd say the with-arguments one is easier to read, but I've far less familiar with stack-based postfix programming.

-----

2 points by akkartik 2019 days ago | link

Yeah, some other polynomial might be a better example. The Pythagorean formula has a clean separation when it comes to which args each term uses. How about the roots of a quadratic equation when you start out with a, b and c on the stack?

-----

2 points by zck 2019 days ago | link

Yes, that is definitely simpler, although the formula is more esoteric. At least, for me, it triggers the "I was taught this in school and haven't used it since" filter.

Maybe something like:

  total-price cost-per-item number-of-items shipping-cost-per-item rush-cost = cost-per-item number-of-items * shipping-cost-per-item number-of-items * + rush-cost +
This doesn't seem super great; it could be refactored to use each thing once, but it's late and I can't think of any better example.

-----

2 points by akkartik 2019 days ago | link

You're right that in general any such example can be refactored to reduce stack operations. That's even a fun game for many Forth programmers to play. (We lispers have our own equivalents.) But it usually makes the code less comprehensible, in my experience.

-----


I've been hanging out on the Handmade Network Discord channel recently. Mostly because I've developed RSI so can't work as much on Mu, and because I'm bored staying home for months on end.

https://handmade.network/manifesto

The site seems to be some sort of offshoot from a channel where a game developer used to screencast himself developing a game: https://handmadehero.org/watch

From what I can piece together, Handmade Network periodically has some sort of Jam for a few days where a few people hack on projects following some theme.

The most recent theme was Lisp: https://handmade.network/blogs/p/7390

Flip was built for the Lisp Jam. You can see a demo about it here: https://discord.com/channels/239737791225790464/485261036308... (though you'll need to sign up to that Discord group)

There was a discussion about it, a sort of debrief after the Jam, that you can read at https://discord.com/channels/239737791225790464/707742863076....

It's already sparked a bunch of ideas for me: https://mastodon.social/@akkartik/104780599045128839. So decent payoff on being bored and finding this community :)

-----

2 points by jsgrahamus 2058 days ago | link

Sounds interesting. Sorry about the RSI. Went to using an ergonomic keyboard, which seems to have squelched it for me.

-----

2 points by akkartik 2073 days ago | link | parent | on: Still alive?

It's still running HN :)

Previous thread: http://arclanguage.org/item?id=21205

Edit: Another thread from 5 years ago: http://arclanguage.org/item?id=19408

-----


This is awesome. Particularly the instructions for rerunning a single test in isolation.

-----

2 points by zck 2148 days ago | link

Thanks! The goal is to make it _way_ easier to reproduce a single test, and investigate what's going wrong.

And it's a testament to the power of Lisp that I didn't have to change much to make it happen.

-----


I just encountered the GraphBLAS project which feels like maybe a more mature approach to this idea: https://www.acm.org/articles/people-of-acm/2019/tim-davis

-----

1 point by akkartik 2153 days ago | link

"See, most data structures aren't really data structures at all. They're _acceleration_ structures _for_ data."

http://acko.net/blog/software-development-as-advanced-damage...

-----


I just remembered that I don't understand this comment. Arc stores forum data in s-expressions, not JSON. How would we expect a JSON parser to work?

Is making it work just a matter of calling the Racket primitive on `rep.template` instead of `template`?

-----

2 points by krapp 2166 days ago | link

I spend all my time with Arc working on the forum, so JSON is more important to me than it might be to someone else who primarily focuses on the language.

I was working on the data export feature, which before just dumped a user's posts and comments as a printed statement, but I wanted it to export JSON because that's more portable. Racket has a really nice JSON parser, unfortunately all the forum data is templated, so I can't just pass it to Racket because it doesn't understand the type. There may be a way to get it to work at that level but I don't know what it is.

So I had to get an old JSON parser for Arc and rewrite a bit of it to be able to generate JSON from forum data. I think it was zck's parser but I'm not entirely sure ATM. It works, but it's an awkward workaround. Any data coming from the forum has to go through one parser, and any other data, such as from a remote API would be going through another (Racket's.)

>Is making it work just a matter of calling the Racket primitive on `rep.template` instead of `template`?

Yes, I had to hunt around to even know that existed, because it's not in the docs.

-----

3 points by rocketnia 2165 days ago | link

Not that it helps you much now, but the best documentation for tagged types (including the `rep` operation) is probably this: http://arclanguage.github.io/ref/type.html

You can get to it from the "Type operations" link in the table of contents. Of course, you might've been drawn to the "Templates" page instead.

And unfortunately, that documentation describes Anarki's stable branch. Making documentation of similar quality for Anarki's master branch might be quite a bit more work. So even if you were familiar with tagged types already, the knowledge that template instances were a tagged type on Anarki master might be hard to discover.

I suppose a more ideal form of the documentation would describe on the "Templates" page that template instances were a tagged type with a certain representation. It could describe the direct implications of that, but it would also have a link to the "Type operations" page for more background.

-----

2 points by krapp 2164 days ago | link

>I suppose a more ideal form of the documentation would describe on the "Templates" page that template instances were a tagged type with a certain representation. It could describe the direct implications of that, but it would also have a link to the "Type operations" page for more background.

That would have been tremendously helpful, can we do that? Who actually maintains the documentation, can it be updated?

-----

1 point by akkartik 2165 days ago | link

Perhaps we should maintain the docs only in plain-text and only within the repo, just because of the operational overheads of managing multiple branches. At least that way they won't seem to lie to a newcomer. I'm curious to hear what others think.

-----

2 points by krapp 2164 days ago | link

There is already a wiki that isn't getting much use[0].

[0]https://github.com/arclanguage/anarki/wiki

-----

1 point by akkartik 2164 days ago | link

It sounds like you mean a JSON generator (emitting JSON) rather than parser (ingesting JSON). Is that right?

-----

2 points by krapp 2164 days ago | link

Yes.

-----


I've always implicitly assumed that tables were just an implementation detail for templates, and assumed they were like objects. I think that might be why it never occurred to me to apply `len` on a template, and why a large program like news.arc never ran into this gap.

I have no objection to your approach of making them more like tables. I also have no objection to updating the documentation to stop referring to them as tables. One of those options might be less work than the other :)

-----

3 points by zck 2168 days ago | link

> I've always implicitly assumed that tables were just an implementation detail for templates, and assumed they were like objects. I think that might be why it never occurred to me to apply `len` on a template, and why a large program like news.arc never ran into this gap.

Ah, interesting! Here's some background that might help understand why having them act like tables might help. `len` wasn't the first function that I found didn't work with templates; it was just the simplest. I was working on adding teardown functionality to unit-test.arc, and I wanted to look at some of the suites that were created, to see if I was adding tests properly. As the templates end up pretty big (one suite with two tests is about 25 lines), and it was late at night, I figured I'd make it simple on myself, and just look at the keys of the template.

This had nothing to do with the desired "production" code, but only with REPL-hacking introspection.

I want to make them reasonably easy to REPL-hack with; whether they're actually tables or not I don't particularly care right now. The most important table functions are probably len, keys, vals, and maybe maptable/each.

-----

2 points by akkartik 2168 days ago | link

How about some rep-hacking rather than REPL-hacking? :D

   (keys rep.tem)

-----

3 points by zck 2167 days ago | link

Haha, that certainly works. It brings up the question of "what Arc internals should people need to know about?". I've never looked into Arc's tagged types until this issue.

-----

2 points by akkartik 2167 days ago | link

My answer to that question has always been, "it depends." The anti-encapsulation ethos (homoiconicity, using lists where other languages may use objects, the entire compiler fitting in one file and being accessible front-and-center, etc.) means that there's always the ability to peel back another layer of the onion when it becomes relevant.

-----

2 points by krapp 2166 days ago | link

I think it's a documentation issue. I think I had to search the forums to find out about it when I was playing with JSON interop. Nowhere on the actual template page in the Arc documentation does it tell you this is a thing.

Some things I've only been able to figure out by studying the compiler or arc source code itself. Granted, that's illuminating, but it's also sometimes really annoying.

-----

2 points by rocketnia 2166 days ago | link

"to find out about it"

Sorry, to find out about which part?

-----

2 points by rocketnia 2165 days ago | link

Ah, I think I understand: The fact that you can call `rep` on a template instance.

-----

1 point by akkartik 2166 days ago | link

Indeed. It never occurred to me to write about this. Would you be interested in writing something up? I can help you wrestle with the documentation system if you like.

-----

3 points by krapp 2168 days ago | link

Templates should just enforce a signature for table fields, but otherwise decompose to tables. I think the issue is that the tables generated by (inst) from a template are annotated as type 'tem when that should (could?) be done with a tag that doesn't actually change the type. Since the template name is passed to the function, you could just build the table with default values without annotating it. You could also delete most of the template functions that just serve as template versions of table functions.

edit: I was also assuming templates type-hinted field values but I don't think they do. Maybe they should?

Also, does anyone else find it a bit odd that a language feature like this is in a lib file rather than being part of the core language?

-----

3 points by akkartik 2168 days ago | link

Small languages take it as a mark of pride to move as much as possible into libraries :) The pitch is that the language is powerful, and nothing shows that like language features as libraries.

I was very proud in Wart that multi-branch ifs were a library feature. And check out this quote in a blog post about Forth:

    : (   41 word drop ; immediate
    ( That was the definition for the comment word. )
    ( Now we can add comments to what we are doing! )
(https://yosefk.com/blog/my-history-with-forth-stack-machines...)

---

I disagree with your vision for templates. If you just want something that behaves like tables, why not just use tables? A helper that fills in default values would be pretty easy to write.

Think about the use case of news.arc. There's a list of 'objects' that need to be serialized to disk and unserialized from disk. What should happen if you change the default for a template in code? Should the default update transparently for existing objects? If so, you need some way to distinguish tables that were generated by templates. Which implies something that manages them throughout their life cycle.

-----

2 points by krapp 2166 days ago | link

>Small languages take it as a mark of pride to move as much as possible into libraries :)

Yeah, I've seen projects that show off the power of a language by doing "x in < 100 lines" that just don't count a remote API call with half a million LOC running on a server or something ;) But with language features like macros and templates that have become ubiquitous, I feel like it's kind of cheating not to just fold them into arc proper.

But that's just me... one thing I've learned being here is that I seem to flow against the culture more than with it, so I can just agree to disagree.

> If you just want something that behaves like tables, why not just use tables?

They are tables, that's what's frustrating. They're tables with metadata. From what I can tell reading earler posts about templates, they used to be something that behaves like tables. Interop between forum data and Racket (and any code where tables are expected) is awkward because that incompatibility has to be worked around, resulting in extra code and extra complexity. Templates need a separate API despite having the same behavior as tables.

>If so, you need some way to distinguish tables that were generated by templates. Which implies something that manages them throughout their life cycle.

Fair enough. But why is it necessary to change their type to do so? Why not make this a feature of tables as a whole, if it's useful? Or tag tables in a way that doesn't change their type, if that's possible?

I understand that tradeoffs have to be made and I'm not trying to be cantankerous or dismiss the value of anyone's work, and no, I couldn't do better myself (yet), which is why I'm commenting on it it rather than making a PR. I'm just wondering if this is the best possible implementation of the concept, given how often I and other people seem to run into issues with it.

-----

2 points by akkartik 2166 days ago | link

Don't worry about sounding dismissive, I totally understand where the questions are coming from.

Tables and objects feel like separate concepts, and they have complementary strengths and weaknesses, and one doesn't subsume the other. To me it seems obvious that if we want to have both, we need them to have different types.

For example, sometimes you want the 'dynamic' ability to set arbitrary keys of metadata on a thing. Sometimes you want the same operation to be an error, by providing a schema. How would a single type do both? No language does so, to my knowledge.

Things should have the same type when they have compatible behavior. When they are incompatible, they shouldn't.

Supporting helpers like len and keys may well still make sense. And as the original story did, this is easy to do.

But in general, having incompatible types easily share functions without sharing too much is an open problem: https://en.wikipedia.org/wiki/Expression_problem A language can easily add a method to many types, or add a new type to many methods. But we don't yet know how to achieve both sides.

And honestly, I think the expression problem isn't important. It doesn't take too much code per method/type. And making it easier just encourages large, bloated codebases.

> ...given how often I and other people seem to run into issues with it.

One thing that might be useful here is a list of issues people have encountered with templates. Maybe we should create a wiki page on GitHub and add to it every time an issue comes up. Then we can have a big-picture view of them and a sense of how many are things people need to learn about Arc, and how many are bugs to be fixed.

I believe Anarki behaves exactly the same as Arc's intent when it comes to templates. The changes that I made here seemed strictly superior to the buggy implementation upstream. But if you disagree you should absolutely feel free to just revert the commits and go back to Arc behavior. I don't use Arc anymore, so my opinions are extremely weakly held, you don't have to bother persuading me. Or, if you have some other specific issue in mind, I'd be happy to be persuaded that I'm wrong.

-----

2 points by rocketnia 2165 days ago | link

"But in general, having incompatible types easily share functions without sharing too much is an open problem: https://en.wikipedia.org/wiki/Expression_problem A language can easily add a method to many types, or add a new type to many methods. But we don't yet know how to achieve both sides."

I'm trying to follow, but I think you and I must have different understandings of the expression problem. That article lists several known solutions to the expression problem. The solution Anarki uses is `defextend`.

What do you mean by "sharing too much"?

Is Anarki's `defextend` technique already encouraging a bloated codebase, or is there some other technique you're thinking of that would do that?

-----

2 points by akkartik 2165 days ago | link

Yeah, I suppose you could say the problem is 'solved'. I think of it as a trade-off with costs. We don't know how to achieve zero cost.

For example, I absolutely agree with you that 2 lines per method to extend every table method to some new type constitutes a solution for us. But if we had a thousand such types and a thousand such methods, it may seem like less of a solution. But then `defextend` would be the victim rather than cause of bloat.

-----

3 points by rocketnia 2164 days ago | link

Ah, you're imagining us having to write and maintain 1000×1000 individual `defextend` forms someday? Yeah, that does seem like a problem that would not feel solved once we got to it. :-p

I don't think that aspect of the expression problem is solvable in a language design. Instead, it's an ongoing conversation in the community. Sometimes the intent of one feature and the intent of another feature interact, leading people to do a nonzero amount of work to figure out the intent of the two features put together. That work is an essential part of what the community is trying to accomplish together, so it's a cost that can't be eliminated. The intent has to be reflected in the code somewhere, so there will be a nonzero amount of code that serves feature-coordinating purposes.

Regardless, I'm optimistic that although the amount of code will be nonzero, it'll still have a manageable size. To the extent we have any kind of consistency around these feature interaction decisions, those consistent principles can develop into abstractions. The only way we'll have 1000×1000 individual intersections to maintain is if we as a community culture are already maintaining 1,000,000 compelling and distinct justifications for them. :)

-----

1 point by akkartik 2164 days ago | link

Indeed. I'm curious: does my interpretation of the expression problem miss what the papers tend to focus on?

-----

2 points by rocketnia 2163 days ago | link

Well... That's a good question.

I haven't read any more than a few papers on it, and maybe only one of those in depth (which I'll mention below). Mostly I'm going by forum threads, wiki articles, and the design choices certain languages make (like Inform's multimethods and Haskell's type classes).

As far as I understand the history, Philip Wadler's work basically defined the strict parameters of the expression problem and explored solutions for it. Separate compilation and the avoidance of dynamic casts were big deals for Wadler for some reason.

That work was focused on Java, where it's easy to define new classes that implement existing interfaces but impossible to implement new interfaces on existing classes.

The solution I'm most familiar with for Java-style languages is the use of object algebras, as described in Oliveira and Cook's "Extensibility for the Masses: Practical Extensibility with Object Algebras" (https://www.cs.utexas.edu/~wcook/Drafts/2012/ecoop2012.pdf). In this approach, when you extend the system with a new type, you define a new interface with a generic type parameter and a factory method for building that type, and you have that interface inherit all the existing factory methods. So you don't have to solve the unsolvable task of implementing a new interface for an existing class, because you're representing your types as type parameters and methods, not simply as classes.

So I think the main subject of research was how best to represent an extensible program's types and functions in a language like Java where the most obvious choices weren't expressive enough. I think it's more of a "how do we allow extensions to be made at all" problem than a "how do we make all the extensions maintainable" problem.

But then, I've really barely scratched the surface of the research, so I could easily be missing stuff like that.

-----

2 points by akkartik 2166 days ago | link

> ... with language features like macros and templates that have become ubiquitous, I feel like it's kind of cheating not to just fold them into arc proper.

Cheating how?

It's totally fine to move something into arc.arc if you want to do that. It's always felt like a non-existent distinction in my mind whether something is under arc.arc or libs/. Is Anarki all language or all standard library? Depends on how you look at it. Why does it matter?

> But that's just me... one thing I've learned being here is that I seem to flow against the culture more than with it, so I can just agree to disagree.

This doesn't feel like a disagreement, more like a language barrier. If I understood better I might know whether I agree or not.

-----

More