[HN Gopher] Some Programming Language Ideas
___________________________________________________________________
Some Programming Language Ideas
Author : todsacerdoti
Score : 96 points
Date : 2025-01-08 18:58 UTC (4 hours ago)
(HTM) web link (jerf.org)
(TXT) w3m dump (jerf.org)
| a1o wrote:
| Is it just me or whatever "Capabilities" is, is not explained at
| all?
| jerf wrote:
| It is not. I didn't want to give a half explanation, but it is
| another case of the increasing difficulty in coming up with
| good Google searches anymore.
|
| https://erights.org/elib/capability/ode/ode-capabilities.htm...
| is a good start.
|
| But you use capabilities all the time... operating system users
| work that way. As a user, you can't "just" execute some binary
| somewhere and thereby get access to parts of the system your
| user doesn't have rights to. (Forget setuid for a second, which
| is intended precisely to get around this, and let's just look
| at the underlying primitive.)
|
| Capabilities in programming languages take the granularity
| further down. You might call some image manipulation code in a
| way that it doesn't have the capability to manipulate the file
| system in general, for example, or call a function to change a
| user's login name with capabilities that _only_ allow changing
| that user, even if another user ID somehow gets in there.
|
| It would be a fairly comprehensive answer to the software
| dependency issues that continue to bubble up; it would matter
| less if a bad actor took over "leftpad" if leftpad was actively
| constrained by the language to _only_ be able to manipulate
| strings, so the worst an actor could do is make it manipulate
| strings the wrong way, rather than start running arbitrary
| code. Or put another way, if the result of the bad actor taking
| the package wasn 't that people got hacked but users started
| getting compile error in file.X:28: library
| "leftpad" tried to open a file without file system capabilities
| compile error in file.X:30: library "leftpad" tried to open a
| socket without network capabilities
|
| which would immediately raise eyebrows.
|
| It's not a new idea, in that E already tried it, and bits and
| pieces of it are _everywhere_ ( "microkernels" is another place
| where you'll see this idea, but at the OS level and implemented
| in languages that have no native concept of the capabilities),
| but for the most part our programming languages do not reflect
| this.
| NeutralForest wrote:
| I think the Austral language tries to do some capability
| based things: https://austral-lang.org/.
| a1o wrote:
| Thanks for this link!
|
| It has this other link that explains more on it :
| https://en.m.wikipedia.org/wiki/Capability-based_security
|
| I think I get it now. I honestly never had heard about this
| before and trying to Google search from the original post I
| was coming up empty.
| RainyDayTmrw wrote:
| For the equivalent in operating systems land, look at the
| respective manual pages for Linux capabilities[1] or OpenBSD
| pledge[2] and unveil[3]. The general idea is that there are
| some operations that might be dangerous, and maybe we don't
| want our program to have unrestricted access to them. Instead,
| we opt-in to the subset that we know we need, and don't have
| access to the rest.
|
| There's some interest in the same thing, but at the programming
| language level. I'm only aware of it being implemented
| academically.
|
| [1]: https://man7.org/linux/man-pages/man7/capabilities.7.html
| [2]: https://man.openbsd.org/pledge.2 [3]:
| https://man.openbsd.org/unveil.2
| mike_hearn wrote:
| It's implemented in Java! .NET tried it too, UNIX file
| descriptors are capabilities, Mach ports are capabilities.
| Capabilities are widely used far outside of academia and have
| been for a long time.
|
| What people often mean when they say this is a so-called pure
| capability system, where there are no ambient permissions at
| all. Such systems have terrible usability and indeed have
| never been made to work anywhere, not even in academia as far
| as I know.
| gpderetta wrote:
| I don't think that Linux capabilities have much to do with
| the capabilities that the OP intends.
|
| In a capabilities system, a program has permission to act on
| any object if it has a reference (aka a capability) to the
| object, there is no other access control. A program acquires
| a capability either by receiving it from is parent (or caller
| in the case of a function) or some other way like message
| passing. There is no other source of capabilities and they
| are unforgeable.
|
| Unix file descriptors act in many ways as capabilities: they
| are inherited by processes from their parents and can be
| passed around via Unix sockets, and grant to the FD holder
| the same permissions to the referenced object as the creator
| of the file descriptor.
|
| Of course as Unix has other ways from creating file
| descriptors other than inheritance and message passing is not
| truly a capabilities system.
| marcosdumay wrote:
| > This is not a new idea, so I won't go deeply into what it is
|
| So, no, the author claims it too.
|
| Capabilities are a way to do access control where the client
| holds the key to access something, instead of the server holds
| a list of what is allowed based on the clients identities.
|
| But when people use that word, they are usually talking about
| fine-grained access control. On a language level, that would
| mean not granting access for example for a library to do
| network connections, even though your program as a whole has
| that kind of access.
| dboreham wrote:
| It's a fancy word for "access things through a handle".
| gpderetta wrote:
| The key property being that everything can only be accessed
| via handles, including, recursively, other handles (i.e. to
| get an handle to an object you need first to already have an
| handle to the handle-giver for that object).
| wslh wrote:
| My 4 cents:
|
| - I like the idea of a multiparadigm programming language (many
| exists) but where you can write part of the code in a different
| language, not trying to embed everything in the same syntax. I
| think in this way you can write code and express your ideas
| differently.
|
| - A [social] programming language where some variables and
| workflows are shared between users [1][2].
|
| - A superreflective programming language inspired by Python,
| Ruby, and others where you can override practically everything to
| behave different. For example, in Python you can override a
| function call for an object but not for the base system,
| globals() dict cannot be overriden. See [3]. In this way you save
| a lot of time writing a parser and the language basic logic.
|
| - A declarative language to stop reinventing the wheel: "I need a
| website with a secure login/logout/forgot_your_password_etc,
| choose a random() template". It doesn't need to be in natural
| language though.
|
| [1] https://blog.databigbang.com/ideas-egont-a-web-
| orchestration...
|
| [2] https://blog.databigbang.com/egont-part-ii/
|
| [3] https://www.moserware.com/2008/06/ometa-who-what-when-
| where-...
| mike_hearn wrote:
| Egont sounds a bit like SQL, no? A social way to share data and
| work with it ... a shared RDBMS where everyone has a user
| account and can create tables/share them with other users,
| built in security, etc. Splat a GUI on top and you have
| something similar.
|
| Modern web frameworks are getting pretty declarative. If you
| want a basic web app with a log in/out page that's not hard to
| do. I'm more familiar with Micronaut than Spring but you'd just
| add: micronaut.security.authentication=cookie
|
| and the relevant dependencies. Now you write a class that
| checks the username/password, or use LDAP, or configure OAuth
| and the /login URL takes a POST of username/password. Write a
| bit of HTML that looks good for your website and you're done.
|
| https://micronaut-projects.github.io/micronaut-security/late...
| wslh wrote:
| > Egont sounds a bit like SQL, no? A social way to share data
| and work with it ... a shared RDBMS where everyone has a user
| account and can create tables/share them with other users,
| built in security, etc. Splat a GUI on top and you have
| something similar.
|
| Yes, SQL or a global spreadsheet. I would say that it is like
| SQL plus a DAG or, we can imagine an aggregation of SQLs. The
| interesting thing is that parts of the global system are only
| recalculated if there is a change, like in a spreadsheet.
|
| > a shared RDBMS where everyone has a user account and can
| create tables/share them with other users, built in security,
| etc. Splat a GUI on top and you have something similar.
|
| We need a little bit more but not much more: security by
| namespaces and/or rows so the same database is shared but you
| can restrict who change what: your "rows" are yours. I think
| something like OrbitDB but with namespaces will be cool.
|
| > Modern web frameworks are getting pretty declarative.
|
| Yes but my proposal was at a higher level. I don't want to
| know what a cookie is when I just want to create a website. I
| am not saying that you can create complex components with
| this idea but you can create common use cases.
| nickpsecurity wrote:
| For relational, look into term-rewriting systems which just keep
| transforming specified relationships into other things. Maude's
| rewriting logic and engine could probably be used for relational
| programming. It's fast, too.
|
| https://maude.cs.illinois.edu/wiki/The_Maude_System
| AnimalMuppet wrote:
| For "value database", it seems to me that the trick is, you can't
| just ship the executable. You have to ship the executable plus
| the stored values, together, as your installation image. Then
| what you run in production is what you tested in staging, which
| is what you debugged on your development system.
|
| I mean, there still may be other things that make this a Bad
| Idea(TM). But I think this could get around the specific issue
| mentioned in the article.
| marcosdumay wrote:
| Basically, it's the Windows registry.
|
| If it's about well-contained applications in a well designed
| (and user-centric) OS with a proper concept of "application"
| and "installation", with a usable enough mechanism, I don't see
| anything that would make it bad.
|
| On Windows it's a disaster. To the point that dumping random
| text files around in Linux works better.
| jinwoo68 wrote:
| It's not very convincing to me when the article talks about truly
| relational language but fails to mention Prolog and anything that
| we learned from it.
| jerf wrote:
| Logic languages are definitely not what I'd expect a
| relational-first language to look like.
|
| What we learned from Prolog is mostly that starting from an
| exponentially-complex primitive and then trying to beat it into
| submission doesn't work at scale. Relational DBs don't have
| that problem. They do go n-squared and n-cubed and so forth
| easily, but there are lots of solutions to that as well.
| jinwoo68 wrote:
| I'm not sure what you mean with "an exponentially-complex
| primitive". In my opinion, Prolog lets you start with simple
| relations (n-squared, using your terms) and then enables you
| to build more complex relations using them.
| 082349872349872 wrote:
| for "Semi-Dynamic Language" it might be worth looking into
| rpython: interpreters written in rpython have two phases, in the
| first phase one has full python semantics, but in the second
| phase everything is assumed to be less dynamic, more restricted
| (the _r_ of rpython?) so the residual interpreter is then
| transpiled to C sources, which, although compiled, can also make
| use of the built-in GC and JIT.
| MillironX wrote:
| I immediately thought of Julia as a semi-dynamic language.
| Julia is a dynamic language, but (as I understand it) the first
| time a function is called with a specific type signature, that
| specific method is JIT compiled as static LLVM.
| Jtsummers wrote:
| Which is then used for future dispatches on that same
| signature and gives it very good performance. Julia is
| dynamic, and definitely beats the 10x slower than C barrier
| jerf mentioned.
|
| For what I was using it for at the time (~3 years ago when I
| used it seriously) it offered performance close to the
| compiled orbital analysis code we had (in more conventional
| languages, Fortran and C) but with the flexibility for
| developing models of Python and other dynamic/interactive
| languages. An excellent tradeoff: very small performance cost
| for better interactivity and flexibility.
| rirze wrote:
| Sans the python compatiblity, rhai is pretty close to what
| rpython is trying to be.
|
| https://github.com/rhaiscript/rhai
| continuational wrote:
| Interesting points!
|
| We're working on a language with some of these ideas:
|
| https://www.firefly-lang.org/
|
| Object capabilities, async calls as easy as sync calls, modular
| monoliths, and (eventually) unified logging.
|
| None of the relational language features though.
|
| Feedback appreciated!
| Animats wrote:
| - Looser functions (badly chosen name)
|
| Timeouts on calls are, as the OP mentions, a thing in Erlang.
| Inter-process and inter-computer calls in QNX can optionally time
| out, and this includes all system calls that can block. Real-time
| programs use such features. Probably don't want it on more than
| that. It's like having exceptions raised in things you thought
| worked.
|
| - Capabilities
|
| They've been tried at the hardware level, and IBM used them in
| the System/38, but they never caught on. They're not really
| compatible with C's flat memory model, which is partly they fell
| out of fashion. Capabilities mean having multiple types of
| memory. Might come back if partially-shared multiprocessors make
| a comeback.
|
| - Production-Level Releases
|
| That's kind of vague. Semantic versioning is a related concept.
| It's more of a tooling thing than a language thing.
|
| - Semi-Dynamic Language
|
| I once proposed this for Python. The idea was that, at some
| point, the program made a call that told the system "Done
| initializing". After that point, you couldn't load more code, and
| some other things that inhibit optimization would be prohibited.
| At that point, the JIT compiler runs, once. No need for the
| horrors inside PyPy which deal with cleanup when someone patches
| one module from another.
|
| Guido didn't like it.
|
| - Value Database
|
| The OP has a good criticism of why this is a bad idea. It's an
| old idea, mostly from LISP land, where early systems saved the
| whole LISP environment state. Source control? What's that?
|
| - A Truly Relational Language
|
| Well, in Python, almost everything is a key/value store. The
| NoSQL people were going in that direction. Then people remembered
| that you want atomic transactions to keep the database from
| turning to junk, and mostly backed off from NoSQL where the data
| matters long-term.
|
| - A Language To Encourage Modular Monoliths
|
| Hm. Needs further development. Yes, we still have trouble putting
| parts together. There's been real progress. Nobody has to keep
| rewriting Vol. I of Knuth algorithms in each new project any
| more. But what's being proposed here?
|
| - Modular Linting
|
| That's mostly a hack for when the original language design was
| botched. View this from the point of the maintenance programmer -
| what guarantees apply to this code? What's been prevented from
| happening? Rust has one linter, and you can add directives _in
| the code_ which allow exceptions. This allows future maintenance
| programmers to see what is being allowed.
| mplanchard wrote:
| > In general, while I can't control how people react to this
| list, should this end up on, say, Hacker News, I'm looking more
| for replies of the form "that's interesting and it makes me
| think of this other interesting idea" and less "that's stupid
| and could never work because X, Y, and Z so everyone stop
| talking about new ideas" or "why hasn't jerf heard of this
| other obscure language that tried that 30 years ago". (Because,
| again, of course I don't know everything that has been tried.)
| bruce343434 wrote:
| As for "capabilities", I'm not sure I fully understand how that
| is advantageous to the convention of passing the helper function
| ("capability") as an argument to the "capable" function.
|
| For instance, in Zig, you can see that a function allocates
| memory (capability) because it requires you to pass an allocator
| that it can call!
|
| I'd like to see if others are more creative than me!
| spencerflem wrote:
| That's pretty much how it plays out, as I understand it.
|
| The trick is making sure that that object is the Only possible
| way to do the thing. And making more features like that, for
| example Networking, or File I/O, etc
| do_not_redeem wrote:
| In Zig it's conventional to pass an allocator, but any code can
| end run around the convention by reaching for page_allocator or
| c_allocactor behind your back. Capabilities upgrade that
| convention into a guarantee.
| racingmars wrote:
| > Value Database
|
| > Smalltalk and another esoteric programming environment I used
| for a while called Frontier had an idea of a persistent data
| store environment. Basically, you could set global.x = 1, shut
| your program down, and start it up again, and it would still be
| there.
|
| Frontier! I played with that way back when on the Mac. Fun times.
|
| But as for programming language with integrated database...
| MUMPS! Basically a whole language and environment (and, in the
| beginning, operating system) built around a built-in global
| database. Any variable name prefixed with ^ is global and
| persistent, with a sparse multi-dimensional array structure to be
| able to organize and access the variables (e.g.
| ^PEOPLE(45,"firstname") could be "Matthew" for the first name of
| person ID 45). Lives on today in a commercial implementation from
| Intersystems, and a couple Free Software implementations
| (Reference Standard M, GT.M, and the GT.M fork YottaDB). The
| seamless global storage is really nice, but the language itself
| is truly awful.
| jonathaneunice wrote:
| Image persistence was one of the cool ideas of Smalltalk. And
| in practice, one of the biggest drawbacks. Cruft and old values
| accumulated steadily, with very little way to find and
| eliminate them. Transient execution has some cons. But on the
| pro side, every run starts from a "clean slate."
| whartung wrote:
| This may fall in the "you think you do, but you don't
| category", but I've always wanted a Smalltalk (or similar,
| not that picky) with a persistent virtual memory.
|
| That is, the VM is mapped to a backing file, changes
| persisted automatically, no "saving", limited by drive space
| (which, nowadays, is a lot). But nowadays we also have vast
| memory space to act as a page cache and working memory.
|
| My contrived fantasy use case was having a simple array name
| "mail", which an array containing all of my email messages
| (in email object, of course). Naturally as you get more mail,
| the array gets longer. Also, as you delete mail, then the
| array shifts. It's no different, roughly, than the classic
| mbox format, save it's not just text, its objects.
|
| You can see if you delete a email, from a large (several
| GBs), there would be a lot of churn. That implies maybe it's
| not a great idea to use that data structure, but that's not
| the point. You CAN use that data structure if you like (just
| like you can use mbox if you like).
|
| Were it to be indexed, that would be done with parallel data
| structures (trees or hashes or whatever).
|
| But this is all done automagically. Just tweaks to pages in
| working memory backed by the disk using the virtual memory
| manager. Lots and lot of potential swapping. C'est la vie, no
| different from anything else. This what happens when you map
| 4TB into a 16GB work space.
|
| The problem with such a system, is how fragile is potentially
| is. Corrupt something and it happily persists that
| corruption, wrecking the system. You can't reboot to fix it.
|
| Smalltalk suffers from that today. Corrupt the image (oops,
| did I delete the Object become: method again?), and its gone
| for good. This is mitigated by having backup images, and the
| changelist to try to bring you back to the brink but no
| further.
|
| I'm guessing a way to do that in this system is to use a copy
| on write facility. Essentially, snapshot the persistent store
| on each boot (or whatever), and present a list of previous
| snapshot at start up.
|
| Given the structure of a ST VM you'd like to think this is
| not that dreadful to work up. I'd like to think a paper
| napkin implementation PoC would be possible, just to see what
| it's like. One of those things were the performance isn't
| really that great, but the modern systems are so fast, we
| don't really notice it in human terms.
|
| But I do think it would be interesting.
| cess11 wrote:
| Have you looked at Pharo? Their git integration makes it
| relatively easy to export and backup parts of your main
| image, and to pull the things back into a fresher one once
| you mess up.
| brabel wrote:
| > with very little way to find and eliminate them.
|
| The best Smalltalk these days is GlamorousToolkit:
| https://gtoolkit.com/
|
| It has a sort of git in it, so you can easily "rollback" your
| image to previous states. So going back and forth in history
| is trivial.
| kweingar wrote:
| The MUMPS database is wild. When I was working in MUMPS, it was
| so easy and fun to whip up an internal tool to share with my
| coworkers. You don't have to give any special thought at all to
| persistence, so you're able to stay in the flow of thinking
| about your business logic.
|
| But as you said, the language itself is almost unbearable to
| use.
| danielhlockard wrote:
| I had a professor who is responsible for a lot of the more
| "modern" MUMPS stuff (lets be real, MUMPS is OLD!). Guy was
| pretty unbearable too.
| lihaoyi wrote:
| Starlark, a variant of Python, can be thought of as semi dynamic:
| all mutation in each file happens once, single threaded, and then
| that file and all its data structures are frozen so downstream
| files can use it in parallel
|
| A lot of "staged" programs can be thought of as semi dynamic as
| well, even things like C++ template expansion or Zig comptime:
| run some logic up front, freeze it, then run the rest of the
| application later
| kibwen wrote:
| My wild idea is that I'd like to see a modern "high-level
| assembler" language that doesn't have a callstack. Just like in
| the olden days, all functions statically allocate enough space
| for their locals. Then, combine this with some semi-convenient
| facility for making sure that local variables for a given
| function always fit into registers; yes, I admit that I'm strange
| when I say that I dream of a language that forces me to do manual
| register allocation. :P But mostly what I want to explore is if
| it's possible to create a ""modern"" structured programming
| language that maps cleanly to assembly, and that provides _no_
| optimization backend at all, but has enough mechanical sympathy
| that it still winds up fast enough to be usable.
| Jtsummers wrote:
| > all functions statically allocate enough space for their
| locals.
|
| Would you still have distinct activation records per call or
| forfeit the ability to have reentrant functions and recursion?
|
| That's one of the main reasons to move to dynamic (as in a call
| stack) allocation of your activation records versus a single
| static allocation per function.
| kibwen wrote:
| In this hypothetical language I'm assuming that recursion is
| unsupported and that if threading is supported at all, then
| each thread has its own copy of every function's locals (or
| at least every function that can be called concurrently;
| structured concurrency might be leveraged to prove that some
| functions don't need to be reentrant, or maybe you just chuck
| a mutex in each function prologue and YOLO). However, while
| enforcing that basic recursion is forbidden isn't too
| difficult (you make the language statically-typed, all names
| lexically-scoped, and don't support forward declarations), it
| does probably(?) mean that you also lose first-class
| functions and function pointers, although I haven't thought
| deeply about that.
| vaylian wrote:
| Why would you like to have this language? Is it about control
| over the execution? About better ways to personally optimize?
| Or just intellectual pleasure? Or is it about reliving the
| olden days of assembly language programming but with a modern
| conveniences?
| kibwen wrote:
| I would simply find pleasure in being able to understand
| basically every level of the stack. For a RISC architecture,
| it's not too hard to get a grasp on how it works. Likewise
| for a simple-enough programming language. The problem(?) is
| that in between these two is an opaque black box--the
| optimization backend, which I feel I have no hope of
| understanding. So instead I wonder if it's possible to have a
| "safe" (safer than C) and "high-level" (more abstractive than
| C) language that is still useful and semi-performant, and I'm
| wondering how much ergonomics would need to be sacrificed to
| get there. It's a thought experiment.
| do_not_redeem wrote:
| Have you thought about what happens if you want to read and
| parse a file? Do you declare the maximum filesize you want to
| support and statically allocate that much memory?
| PaulDavisThe1st wrote:
| No, you have a scoped pointer to dynamically allocated
| memory; when the scoped pointer is destroyed/cleaned
| up/released at the end of the function, it releases the
| allocated memory.
| kibwen wrote:
| I'm not intending to imply that the language I'm describing
| can't support heap-allocated memory; Rust shows us that it's
| even possible to do so without having to manually deallocate,
| if you're okay with a single-ownership discipline (which is a
| rather simple analysis to implement, as long as you don't
| also want a borrow checker along for the ride). Instead, this
| is about trying to make a language that makes it easy to keep
| locals in registers/cache, rather than relying on the
| compiler backed to do register allocation and hoping that
| your CPU can handle all that cache you're thrashing.
| aziis98 wrote:
| I agree about relational languages. It's absurd when I think that
| SQL and Datalog came from the same foundations of relational
| calculus. It's just so much lost expressive power.
|
| I really like what PRQL [1] did, at least it makes table
| operations easily chainable. Another one that comes to mind is
| Datomic [2].
|
| [1]: https://prql-lang.org/
|
| [2]: https://docs.datomic.com/peer-tutorial/query-the-data.html
| vkazanov wrote:
| Uh... given the beauty of relational algebra I don't understand
| how we ended up with the ugly mess of sql.
| PaulHoule wrote:
| This makes me want to throw up
|
| https://www.postgresql.org/docs/current/queries-with.html
| refset wrote:
| If you would like some exposure therapy:
| https://databasearchitects.blogspot.com/2024/12/advent-of-
| co... [0]
|
| [0] Recent discussion
| https://news.ycombinator.com/item?id=42577736
| PaulHoule wrote:
| Some people might think it is crazy but I like wrapping
| queries like that up in JooQ so I can write
| recursiveQuery(table, linkClause, selectCaluse)
| mamcx wrote:
| I explore the idea with https://tablam.org (relational +
| array). I even toyed with making relational queries to make
| types: data Person = User ?except .password
| ?select .name, .id + Customer ?select .name, .customer_id AS
| .id
|
| So the types are in full sync when changes on the schema
| happen. And all of this is type safe.
| PaulHoule wrote:
| I was struggling with doing interesting things with the
| semantic web circa 2007 and was thinking "OWL sucks" and
| looking at Datalog as an alternative. At that time Datalog was
| an obscure topic and hard to find information about it. 10
| years later it was big.
|
| (Funny after years of searching I found somebody who taught me
| how to do really complex modelling in OWL DL but from reading
| the literature I'm pretty sure the average PhD or prof in the
| field has no idea.)
| PaulHoule wrote:
| I like a lot of these ideas.
|
| "Semi-dynamic" is one of the most common architectures there is
| for large & complex systems. AAA games are usually written in a
| combination of C++ and a scripting language. GNU Emacs is a Lisp
| application with a custom interpreter that is optimized for
| writing a text editor. Python + C is a popular choice as well as
| Java + Groovy or Clojure, I've even worked with a Lua + FORTRAN
| system.
|
| I also think "parsers suck". It should be a few hundred lines at
| most, including the POM file, to add an "unless" statement to the
| Java compiler. You need to (1) generate a grammar which
| references the base grammar and adds a single production, (2)
| create a class in the AST that represents the "unless" statement
| and (3) add an transformation that rewrites
| unless(X) {...} -> if(!X) {...}
|
| You should be able to mash up a SQL grammar and the Java grammar
| so you can write var statement = <<<SELECT *
| FROM page where id=:pageId>>>;
|
| this system should be able to export a grammar to your IDE. Most
| parser generators are terribly unergonomic (cue the event-driven
| interface of yacc) and not accessible to people who don't have a
| CS education (if you need a bunch of classes to represent your
| AST shouldn't these get generated from your grammar?) When you
| generate a parser you should get an unparser. Concrete syntax
| trees are an obscure data structure but were used in obscure RAD
| tools in the 1990s that would let you modify code visually and
| make the kind of patch that a professional programmer would
| write.
|
| The counter to this you hear is that compile time is paramount
| and there's a great case for that in large code bases. (I had a
| system with a 40 minute build) Yet there's also a case that
| people do a lot of scripty programming and trading compile time
| for a ergonomics can be a win (see Perl and REBOL)
|
| I think one goal in programming languages is to bury Lisp the way
| Marc Anthony buried Caesar. Metaprogramming would be a lot more
| mainstream if it was combined with Chomksy-based grammars,
| supported static typing, worked with your IDE and all that.
| Graham's _On Lisp_ is a brilliant book (read it!) that left me
| disappointed in the end because he avoids anything involving deep
| tree transformations or compiler theory: people do much more
| advanced transformations to Java bytecodes. It might be easier to
| write those kind of transformations if you had an AST comprised
| of Java objects instead of the anarchy of nameless tuples.+
| mike_hearn wrote:
| Totally agree that programming languages are a bit stagnant, with
| most new features being either trying to squeeze a bit more
| correctness out via type systems (we're well into diminishing
| returns here at the moment), or minor QoL improvements. Both are
| useful and welcome but they aren't revolutionary.
|
| That said, here's some of the feedback of the type you said you
| didn't want >8)
|
| (1) Function timeouts. I don't quite understand how what you want
| isn't just exceptions. Use a Java framework like Micronaut or
| Spring that can synthesize RPC proxies and you have things that
| look and work just like function calls, but which will throw
| exceptions if they time out. You can easily run them async by
| using something like "CompletableFuture.supplyAsync(() ->
| proxy.myCall(myArgs))" or in Kotlin/Groovy syntax with a static
| import "supplyAsync { proxy.myCall(myArgs) }". You can then
| easily wait for it by calling get() or skip past it. With virtual
| threads this approach scales very well.
|
| The hard/awkward part of this is that APIs are usually defined
| these days in a way that doesn't actually map well to standard
| function calling conventions because they think in terms of
| POSTing JSON objects rather than being a function with arguments.
| But there are tools that will convert OpenAPI specs to these
| proxies for you as best they can. Stricter profiles that result
| in more idiomatic and machine-generatable proxies aren't that
| hard to do, it's just nobody pushed on it.
|
| (2) Capabilities. A language like Java has everything needed to
| do capabilities (strong encapsulation, can restrict reflection).
| A java.io.File is a capability, for instance. It didn't work out
| because ambient authority is needed for good usability. For
| instance, it's not obvious how you write config files that
| contain file paths in systems without ambient authority. I've
| seen attempts to solve this and they were very ugly. You end up
| needing to pass a lot of capabilities down the stack, ideally in
| arguments but that breaks every API ever designed so in reality
| in thread locals or globals, and then it's not really much
| different to ambient authority in a system like the
| SecurityManager. At least, this isn't really a programming
| language problem but more like a standard library and runtime
| problem.
|
| (3) Production readiness. The support provided by app frameworks
| like Micronaut or Spring for things like logging is pretty good.
| I've often thought that a new language should really start by
| taking a production server app written in one of these frameworks
| and then examining all the rough edges where the language is
| mismatched with need. Dependency injection is an obvious one -
| modern web apps (in Java at least) don't really use the 'new'
| keyword much which is a pretty phenomenal change to the language.
| Needing to declare a logger is pure boilerplate. They also rely
| heavily on code generators in ways that would ideally be done by
| the language compiler itself. Arguably the core of Micronaut _is_
| a compiler and it _is_ a different language, one that just
| happens to hijack Java infrastructure along the way!
|
| What's interesting about this is that you could start by forking
| javac and go from there, because all the features already exist
| and the work needed is cleaning up the resulting syntax and
| semantics.
|
| (4) Semi-dynamic. This sounds almost exactly like Java and its
| JIT. Java is a pretty dynamic language in a lot of ways. There's
| even "invokedynamic" and "constant dynamic" features in the
| bytecode that let function calls and constants be resolved in
| arbitrarily dynamic ways at first use, at which point they're
| JITd like regular calls. It sounds very similar to what you're
| after and performance is good despite the dynamism of features
| like lazy loading, bytecode generated on the fly, every method
| being virtual by default etc.
|
| (5) There's a library called Permazen that I think gets really
| close to this (again for Java). It tries to match the feature set
| of an RDBMS but in a way that's far more language integrated, so
| no SQL, all the data types are native etc. But it's actually used
| in a mission critical production application and the feature set
| is really extensive, especially around smooth but rigorous schema
| evolution. I'd check it out, it certainly made me want to have
| that feature set built into the language.
|
| (6) Sounds a bit like PL/SQL? I know you say you don't want SQL
| but PL/SQL and derivatives are basically regular programming
| languages that embed SQL as native parts of their syntax. So you
| can do things like define local variables where the type is
| "whatever the type of this table column is" and things like that.
| For your example of easily loading and debug dumping a join, it'd
| look like this: DECLARE -- Define a
| custom record type for the selected columns TYPE
| EmpDept IS RECORD ( name
| employees.first_name%TYPE, salary
| employees.salary%TYPE, dept
| departments.department_name%TYPE ); empDept
| EmpDept; BEGIN -- Select columns from the
| joined tables into the record SELECT e.first_name,
| e.salary, d.department_name INTO empDept FROM
| employees e JOIN departments d ON e.department_id =
| d.department_id WHERE e.employee_id = 100;
| -- Output the data DBMS_OUTPUT.PUT_LINE('Name: ' ||
| empDept.name); DBMS_OUTPUT.PUT_LINE('Salary: ' ||
| empDebt.salary); DBMS_OUTPUT.PUT_LINE('Department: '
| || emptDebt.name); END;
|
| It's not a beautiful language by any means, but if you want a
| natively relational language I'm not sure how to make it moreso.
|
| (7) I think basically all server apps are written this way in
| Java, and a lot of client (mobile) too. It's why I think a
| language with integrated DI would be interesting. These
| frameworks provide all the features you're asking for already
| (overriding file systems, transactions, etc), but you don't need
| to declare interfaces to use them. Modern injectors like Avaje
| Inject, Micronaut etc let you directly inject classes. Then you
| can override that injection for your tests with a different
| class, like a subclass. If you don't want a subtyping
| relationship then yes you need an interface, but that seems OK if
| you have two implementations that are really so different they
| can't share any code at all. Otherwise you'd just override the
| methods you care about.
|
| Automatically working out the types of parameters sounds a bit
| like Hindley-Milner type inference, as seen in Haskell.
|
| (8) The common way to do this in the Java world is have an
| annotation processor (compiler plugin) that does the lints when
| triggered by an annotation, or to create an IntelliJ plugin or
| pre-canned structural inspection that does the needed AST
| matching on the fly. IntelliJ's structural searches can be saved
| into XML files in project repositories and there's a pretty good
| matching DSL that lets you say things like "any call to this
| method with arguments like that and which is inside a loop should
| be flagged as a warning", so often you don't need to write a
| proper plugin to find bad code patterns.
|
| I realize you didn't want feedback of the form "but X can do this
| already", still, a lot of these concepts have been explored
| elsewhere and could be merged or refined into one super-language
| that includes many of them together.
| billytetrud wrote:
| You might be interesting in looking at the Lima programming
| language: http://btetrud.com/Lima/Lima-Documentation.html . It
| has ideas that cover some of these things. For example, it's
| intended to operate with fully automatic optimization. This
| assumption allows shedding lots of complexity that arises from
| needing to do the same logical thing in multiple ways that differ
| in their physical efficiency characteristics. Like instead of
| having 1000 different tree classes, you have 1 and optimisers can
| then look at your code and decide what available tree structures
| make most sense in each place. Related to your async functions
| idea, it does provide some convenient ways of handling these
| things. While functions are just normal functions, it has a very
| easy way to make a block of async (using "thread") and provides
| means of capturing async errors that result from that.
| tmtvl wrote:
| > _Some Lisps may be able to do all this, although I don't know
| if they quite do what I'm talking about here; I'm talking about
| there being a very distinct point where the programmer says "OK,
| I'm done being dynamic" for any given piece of code._
|
| In Common Lisp there are tricks you can pull like declaring
| functions in a lexical scope (using labels or flet) to remove
| their lookup overhead. But CL is generally fast enough that it
| doesn't really matter much.
| kazinator wrote:
| You can declaim inline a toplevel function. That doesn't
| necessarily mean that it will be integrated into callers. Among
| the possible effects is that the dynamism of reference can be
| culled away. If a function A calls B where B is declaimed
| inline then A can be compiled to assume _that_ B definition.
| (Such that if B is redefined at run-time, A can keep calling
| the old B, not going through the # 'B function binding
| lookup.).
|
| I seem to remember that Common Lisp compilers are allowed to do
| this for functions that are in the same file even if they are
| not declaimed inline. If A and B are in the same file, and B is
| not declaimed _notinline_ (the opposite of _inline_ ), then A
| can be translated to assume the B definition.
|
| So all your helper functions in a Lisp module are allowed to be
| called more efficiently, not having to go through the function
| binding of the symbol.
| fanf2 wrote:
| Interesting that E is cited under "capabilities", but not under
| "loosen up the functions". E's eventual-send RPC model is
| interesting in a number of ways. If the receiver is local then it
| works a bit like a JavaScript callback in that there's an event
| loop driving execution; if it's remote then E has a clever
| "promise pipelining" mechanism that can hide latency. However E
| didn't do anything memorable (to me at least!) about handling
| failure, which was the main point of that heading.
|
| For "capabilities" and "A Language To Encourage Modular
| Monoliths", I like the idea of a capability-secure module system.
| Something like ML's signatures and functors, but modules can't
| import, they only get access to the arguments passed into a
| functor. Everything is dependency injection. The build system
| determines which modules are compiled with which dependencies
| (which functors are passed which arguments).
|
| An existing "semi-dynamic language" is CLOS, the Common Lisp
| object system. Its metaobject protocol is designed so that there
| are clear points when defining or altering parts of the object
| system (classes, methods, etc.) at which the result is compiled,
| so you know when you pay for being dynamic. It's an interesting
| pre-Self design that doesn't rely on JITs.
|
| WRT "value database", a friend of mine used to work for a company
| that had a Lisp-ish image-based geospatial language. They were
| trying to modernise its foundations by porting to the JVM. He had
| horror stories about their language's golden image having
| primitives whose implementation didn't correspond to the source,
| because of decades of mutate-in-place development.
|
| The most common example of the "value database" or image-based
| style of development is in fact your bog standard SQL database:
| DDL and stored procedures are very much mutate-in-place
| development. We avoid the downsides by carefully managing
| migrations, and most people prefer not to put lots of cleverness
| into the database. The impedance mismatch between database
| development by mutate-in-place and non-database development by
| rebuild and restart is a horribly longstanding problem.
|
| As for "a truly relational language", at least part of what they
| want is R style data frames.
| Joel_Mckay wrote:
| Marklar
|
| https://www.youtube.com/watch?v=BSymxjrzdXc
|
| I found it amusing most of the language is supposedly
| contextually polymorphic by definition. =3
| stuhood wrote:
| We have built something that hits on points 1, 3, 5, and 7 at
| https://reboot.dev/ ... but in a multi-language framework
| (supporting Python and TypeScript to start).
|
| The end result is something that looks a lot like distributed,
| persistent, transactional memory. Rather than explicit
| interactions with a database, local variable writes to your state
| are transactionally persisted if a method call succeeds, even
| across process/machine boundaries. And that benefits point 7,
| because transactional method calls compose across
| team/application boundaries.
|
| [1] Loosen Up The Functions [3] Production-Level Releases [5]
| Value Database [7] A Language To Encourage Modular Monoliths
| masfuerte wrote:
| This seems to be similar to Azure Durable Functions:
|
| https://learn.microsoft.com/en-us/azure/azure-functions/dura...
| stuhood wrote:
| They are related, for sure. But one of the biggest
| differences is that operations affecting multiple Reboot
| states are transactional, unlike Azure's "entity functions".
|
| Because Azure entity functions are not updated
| transactionally, you are essentially always implementing the
| saga pattern: you have to worry about cleaning up after
| yourself in case of failure.
|
| In Reboot, transactional function calls automatically roll
| back all state changes if they fail, without any extra
| boilerplate code. Our hypothesis is that that enables a large
| portion of an application to skip worrying about failure
| entirely.
|
| Code that has side-effects impacting the outside world can be
| isolated using our workflow mechanism (effectively durable
| execution), which can themselves be encapsulated inside of
| libraries and composed. But we don't think that that is the
| default mode developers should be operating in.
| cpeterso wrote:
| The section about language support for modular monoliths reminds
| me of John Lakos's _" Large-Scale C++ Software Design"_, which
| focuses on the physical design/layout of large C++ projects to
| enforce interfaces and reduce coupling and compilation time.
| Example recommendations include defining architecture layers
| using subdirectories and the PImpl idiom. It's pretty dated
| (1996, so pre-C++98), but still a unique perspective on an
| overlooked topic.
|
| https://www.amazon.com/dp/0201633620
| dboreham wrote:
| Folks, this is not a process that converges. We've now had 60
| years of language design, use and experience. We're not going to
| get to an ideal language because there are (often initially
| hidden) tradeoffs to be made. Everyone has a different idea of
| which side of each tradeoff should be taken. Perhaps in the
| future we can get AI to generate and subsequently re-generate
| code, thereby avoiding the need to worry too much about language
| design (AI doesn't care that it constantly copies/pastes or has
| to refactor all the time).
| TrianguloY wrote:
| I'll throw another idea here I've been thinking from a time now.
|
| Most languages have a while construct and a do-while.
| while(condition){block}; do{block}while(condition);
|
| The while is run as ... start:
| condition branch-if-false > end block
| branch-always > start end: ...
|
| And the do-while switches the order: ...
| start: block condition branch-if-true >
| start ...
|
| The issue with the while is that more often than not you need to
| do some preparations before the condition. So you need to move
| that to a function, or duplicate it before and inside the loop.
| Do-while doesn't help, since with that you can't do anything
| after the condition. The alternative is a while(true) with a
| condition in the middle. while(true){
| prepare; if(!check) break; process }
|
| But what if there was a language construct for this? Something
| like do{prepare}while(condition){process}
|
| Is there a language that implements this somehow? (I'm sure there
| is, but I know no one)
|
| The best thing is that this construct can be optimized in
| assembly perfectly: ... jump-always >
| start after: process start: prepare
| condition branch-if-true > after ...
| smitty1e wrote:
| At what point are you just doing async and coroutines?
| do_not_redeem wrote:
| Interesting idea, but once you add scoping: do
| { let value = prepare(); } while
| (value.is_valid) { process(value); }
|
| Can the second block of the do-while see `value` in its lexical
| scope? If yes, you have this weird double brace scope thing.
| And if no, most non-trivial uses will be forced to fall back to
| `if (...) break;` anyway, and that's already clear enough imo.
| TrianguloY wrote:
| The scope should be unique, yes. In your example value should
| be visible.
|
| Your are right about the word double braces, but I can't
| think of an alternate syntax other than just removing the
| braces around the while. But in that case it may seem odd to
| have a keyword that can only be used inside a specific
| block...wich is basically a macro for a if(.)break; Maybe I'm
| too used to the c/java syntax, maybe with a different way of
| defining blocks?
| adamc wrote:
| That seems more like a programmer expectations issue than
| something fundamental. Essentially, you have "do (call some
| function that returns a chunk of state) while (predicate that
| evaluates the state) ..."
|
| Hard to express without primitives to indicate that, maybe.
| PaulDavisThe1st wrote:
| Let me FTFY: do { let value =
| prepare(); while (value.is_valid) {
| process(value); }
| digging wrote:
| I don't write a lot of while loops so this is just a bit
| unfamiliar to me, but I'm not really understanding how this
| isn't the same as `do{block}while(condition);`? Could you give
| a simple example of what kind of work `prepare` is doing?
| TrianguloY wrote:
| Think of a producer (a method that returns data each time you
| request one, like reading a file line by line or extracting
| the top of a queue for example) that you need to parse and
| process until you find a special element that means "stop".
|
| Something like do{
| raw_data=produce.get() data=parse(raw_data)
| }while(data!=STOP){ process(data) }
|
| I'm aware this example can be trivially replaced with a
| while(data=parse(producer.get())){process(data)} but you are
| forced to have a method, and if you need both the raw and
| parsed data at the same time, either you mix them into a
| wrapper or you need to somehow transfer two variables at the
| same time from the parse>condition>process
|
| A do-while here also has the same issue, but in this case
| after you check the condition you can't do any processing
| afterwards (unless you move the check and process into a
| single check_and_process method...which you can totally do
| but again the idea is to not require it)
| jolmg wrote:
| You mean like a shell's while-do-done? It's just about allowing
| statements as the conditions, rather than just a single
| expression. Here's an example from a repl I wrote:
| repl_prompt="${repl_prompt-repl$ }" while printf
| "%s" "$repl_prompt" read -r line do eval
| "$line" done echo
|
| The `printf` is your `prepare`.
|
| This should also be doable in languages where statements are
| expressions, like Ruby, Lisp, etc.
|
| Here's a similar Ruby repl: while (
| print "repl> " line = gets ) do result =
| eval line puts result.inspect end puts
| TrianguloY wrote:
| Exactly, here you are basically keeping it as a while with a
| condition but allowing it to be any code that at the end
| returns a boolean, although you need to make sure that
| variables defined in that block can be used in the do part.
|
| Sidenote: I wasn't aware that shell allows for multiple
| lines, good to know!
| url00 wrote:
| Not quite the same but almost feels like the BEGIN block in
| awk.
| brabel wrote:
| I suspect you'll love Common Lisp's LOOP:
| https://gigamonkeys.com/book/loop-for-black-belts
|
| Example: (loop repeat 8 ;;
| init x, then decide how to "prepare" next iteration
| for x = 0 then (+ (* 2 x) 1) collect x)
| (0 1 3 7 15 31 63 127)
|
| You can insert a condition check in the middle, of course:
| (loop repeat 8 for x = 0 then (+ (* 2 x) 1)
| ;; but stop if x gets too big while (< x
| 100) collect x) (0 1 3 7 15 31
| 63)
|
| And much, much more. It's the ultimate loop construct.
| tajpulo wrote:
| I think we have a similar way of thinking. I once wrote a blog
| post about a for loop extension (based on Golang for
| illustration) [0]. values := []string{"hello",
| "world"} for v, value := range values {
| fmt.Printf("%s", value); } inter {
| fmt.Printf(",") } before { fmt.Printf("[")
| } after { fmt.Println("]") } empty {
| fmt.Println("(nothing found)") }
|
| [0] https://lukas-prokop.at/articles/2024-04-24-for-loop-
| extensi...
| cdhdc wrote:
| Eiffel has the loop structure.
|
| from <initialization statements> until <termination condition>
| loop <group of statements> end
| teo_zero wrote:
| In some way it's the dual of break, in that you want to jump
| into the middle of the loop, while break is to jump out of it.
|
| Let's rewrite the loop this way, with 'break' expanded to
| 'goto': while (true) { prepare...
| if (!cond) goto exitpoint; process... }
| exitpoint:
|
| The dual would be: goto entrypoint; do {
| process... entrypoint: prepare... }
| while(cond);
|
| Both constructs need two points: where the jump begins and
| where it lands. The 'break' is syntactic sugar that removes the
| need to specify the label 'exitpoint'. In fact with 'break' the
| starting point is explicit, it's where the 'break' is, and the
| landing point is implicit, after the closing '}'.
|
| If we want to add the same kind of syntactic sugar for the
| jump-in case, the landing point must be explicit (no way for
| the compiler to guess it), so the only one we can make implicit
| is the starting point, that is where the 'do' is.
|
| So we need: a new statement, let's call it 'entry', that is the
| dual of 'break' and a new semantic of 'do' to not start the
| loop at the opening '{' but at 'entry'. do {
| process... entry; prepare... } while
| (cond);
|
| Is it more readable than today's syntax? I don't know...
| scotty79 wrote:
| When we are on subject of loops... I'd love to have 'else'
| block for loops that runs when the loop had zero iterations.
| scotty79 wrote:
| In Scala you can do: while { prepare;
| condition } do { process }
|
| This runs all 3 in order every iteration but quits if condition
| evaluates to false. It just uses the fact that value of a block
| is the value of the last expression in the block.
|
| Scala has a lot of syntax goodies although some stuff is
| exotic. For example to have a 'break' you need to import it and
| indicate where from exactly you want to break out of.
| int_19h wrote:
| C-style for-loop is kinda sorta this. Although the "prepare"
| part has to be an expression rather than a statement, given
| that you have the comma operator and ?: you can do a lot there
| even in C. In C++, you can always stick a statement in
| expression context by using a lambda. So: for
| ([]{ /*prepare*/ }(); /*condition*/;) {
| /*body*/ }
|
| However, the most interesting take on loops that I've seen is
| in Sather, where they are implemented on top of what are,
| essentially, coroutines, with some special facilities that make
| it possible to exactly replicate the semantics of the usual
| `while`, `break` etc in this way:
| https://www.gnu.org/software/sather/docs-1.2/tutorial/iterat...
| andyferris wrote:
| For semi-dynamic language, Julia definitely took the approach of
| being a dynamic language that can be (and is) JITed to excellent
| machine code. I personally have some larger projects that do a
| lot of staged programming and even runtime compilation of user-
| provided logic using Julia. Obviously the JIT is slower to
| complete than running a bit of Lua or whatever, but the speed
| after that is phenomenal and there's no overhead when you run the
| same code a second time. It's pretty great and I'd love to see
| more of that ability in other languages!
|
| Some of the other points resonate with me. I think sensible
| dynamic scoping would be an easy way to do dependency injection.
| Together with something like linear types you could do
| capabilities pretty smoothly, I think. No real reason why you
| couldn't experiment with some persistent storage as one of these
| dependencies, either. Together with a good JIT story would make
| for a good, modular environment.
| andyferris wrote:
| Oh and Zig is another option for allowing injections that are
| checked when used at a call site rather than predefined through
| interfaces.
|
| AFAIK it doesn't have closures (it's too C-like) so you need to
| use methods for all your (implicit) interfaces, but that's
| okay...
|
| I think the "exemplars" could be automatically yoinked from
| documentation and tests and existing usage of the function in
| the code base. Work needs to be done on the IDE front to make
| this accessible to the user.
| gpderetta wrote:
| Well OP, are you me? everything you listed is also in my short
| wishlist for a programming language (well except for the value
| database, once to you have first class relational tables in your
| language, persistence can be tied to the table identity, doesn't
| need to be implicit).
|
| Capabilities and dynamic scoping for "modularisation" nicely lead
| to implicit variables instead of truly global dynamically scoped
| variables. Implicit variables also probably work well to
| implement effect systems which means well behaved asyncs.
|
| Edit: other features I want:
|
| - easy embedding in other low level languages (c++ specifically)
|
| - conversely, easy to embed functions written in another language
| (again c++).
|
| - powerful, shell-like, process control system (including process
| trees and pipelines), including across machines.
|
| - built-in cooperative shared memory concurrency, and preemptive
| shared nothing.
|
| - built-in distributed content addressed store
|
| I guess I want Erlang :)
| losvedir wrote:
| I love these ideas! I've been thinking about the "fully
| relational" language ever since I worked with some product folks
| and marketers at my start up 15 years ago who "couldn't code" but
| were wizards at cooking up SQL queries to answer questions about
| what was going on with our users and product. There was a
| language written in rust, Tablam[0] that I followed for a while,
| which seemed to espouse those ideas, but it seems like it's not
| being owrked on anymore. And Jamie from Scattered Thoughts[1] has
| posted some interesting articles in that direction as well. He
| used to work on the old YC-company/product LightTable or Eve or
| something, which was in the same space.
|
| I've also always thought Joe Armstrong's (RIP) thought of "why do
| we need modules" is really interesting, too. There's a language
| I've seen posted on HN here a couple times that seems to go in
| that approach, with functions named by their normalized hash
| contents, and referred to anywhere by that, but I can't seem to
| remember what it's called right now. Something like "Universe" I
| think?
|
| [0] https://github.com/Tablam/TablaM [1] https://www.scattered-
| thoughts.net [2] https://erlang.org/pipermail/erlang-
| questions/2011-May/05876...
| stux wrote:
| > with functions named by their normalized hash contents, and
| referred to anywhere by that, but I can't seem to remember what
| it's called right now. Something like "Universe" I think?
|
| Unison: https://www.unison-lang.org/docs/the-big-idea/
| cmontella wrote:
| > A Truly Relational Language... Value Database
|
| I helped on a language called Eve about 10 years ago. A truly
| relational language was exactly what that language was supposed
| to be, or at least that's what we were aiming at as a solution
| for a user-centric programming language.
|
| https://witheve.com
|
| The language we came up with was sort of like Smalltalk + Prolog
| + SQL. Your program was a series of horn clauses that was backed
| by a Entity-Attribute-Value relational database. So you could
| write queries like "Search for all the clicks and get those whose
| target is a specific id, then as a result create a new fact that
| indicates a a button was pressed. Upon the creation of that fact,
| change the screen to a new page". We even played around with
| writing programs like this in natural language before LLMs were a
| thing (you can see some of that here
| https://incidentalcomplexity.com/2016/06/10/jan-feb/)
|
| Here's a flappy bird game written in that style:
| https://play.witheve.com/#/examples/flappy.eve
|
| It's very declarative, and you have to wrap you brain around the
| reactivity and working with collections of entities rather than
| individual objects, so programming this way can be very
| disorienting for people used to imperative OOP langauges.
|
| But the results are that programs are much shorter, and you get
| the opportunity for really neat tooling like time travel
| debugging, where you roll the database back to a previous point;
| "what-if" scenarios, where you ask the system "what would happen
| if x were y" and you can potentially do that for many values of
| y; "why not" scenarios, where you ask the system why a value was
| not generated; value providence, where you trace back how a value
| was generated... this kind tooling that just doesn't exist with
| most languages due to how they languages are built to throw away
| as much information away as possible on each stage of
| compilation. The kind of tooling I'm describing requires keeping
| and logging information about your program, and then leveraging
| it at runtime.
|
| Most compilers and runtimes throw away that information as the
| program goes through the compilation process and as its running.
| There is a cost to pay in terms of memory and speed, but I think
| Python shows that interpretation speed is not that much of a
| barrier to language adoptions.
|
| But like I said, that was many years ago and that team has
| disbanded. I think a lot of what we had in Eve still hasn't
| reached mainstream programming, although some of what we were
| researching found its way into Excel eventually.
|
| > Loosen Up The Functions... Capabilities... Production-Level
| Releases... Semi-Dynamic Language... Modular Monoliths
|
| I really like where the author's head at, I think we have similar
| ideas about programming because I've been developing a language
| called Mech that fits these descriptors to some degree since Eve
| development was shut down.
|
| https://github.com/mech-lang/mech
|
| So this language is not supposed to be relational like Eve, but
| it's more like Matlab + Python + ROS (or Erlang if you want to
| keep it in the languages domain).
|
| I have a short 10 min video about it here:
| https://www.hytradboi.com/2022/i-tried-rubbing-a-database-on...
| (brief plug for HYTRADBOI 2025, Jamie also worked on Eve, and if
| you're interested in the kinds of thing the author is, I'm sure
| you'll find interesting videos at HYTRADBOI '22 archives and
| similarly interested people at HYTRADBOI '25), but this video is
| out of date because the language has changed a lot since then.
|
| Mech is really more of a hobby than anything since I'm the only
| one working on it aside from my students, who I conscript, but if
| anyone wants to tackle some of these issues with me I'm always
| looking for collaborators. If you're generally interested in this
| kind of stuff drop by HYTRADBOI, and there's also the Future Of
| Coding slack, where likeminded individuals dwell:
| https://futureofcoding.org. You can also find this community at
| the LIVE programming workshop which often coincides with SPLASH:
| https://liveprog.org
___________________________________________________________________
(page generated 2025-01-08 23:01 UTC)