What would a language that "always compiles" and "...
# thinking-together
n
What would a language that "always compiles" and "never crashes" look like? Where there is no such thing as a syntax error, type error, or runtime error? Where every text string (or AST) is executable? I'm thinking this can be done with a compositional approach to errors: instead of crashing when a result is undefined (due to syntactic or semantic errors), return an "undefined" value, which in turn poisons all of the expressions it is used within, similar to how NaN works. Would such a semantics have merit? It would let you run incomplete programs (like Hazel), and also let you recover from errors (with the equivalent of an isNaN() function). But perhaps most importantly, it would be a good foundation for a live & persistent programming environment: if there is no need for code to be checked prior to execution, then there doesn't need to be a "compile time". This doesn't make programs harder to reason about: a programmer can still use static analysis tools (incl. "type inference") to understand and enforce runtime behaviour (e.g. verifying/requiring the absence of "undefined").
πŸ‘ 2
b
This pattern is common in dataflow languages
βž• 1
I also think they do it in go
n
The title of that Go post is perhaps deceiving, since their approach to errors is just the same old approach that C code uses: an error is a separate return value that must be checked in order to be noticed. It is not compositional, and Go programs still crash in a myriad of ways.
b
LabVIEW (a dataflow language) has errors that are just another value returned, although it does not always compile because it runs a type propagation algorithm that will break your code if the types don't match up. But it is always compiling in the background, ready to run if it can. If an error is returned, the program doesn't crash and they aren't exceptions. You can ignore the error, process it, generate a more specific one, etc. However, it still has the problem of the developer not knowing what errors could be generated, which is a problem in every language I have ever used, and a tough problem. I'd love a language that specifically separated functions between those that can and cannot return errors. Functions that can return errors should return them as values and the documentation should list exactly why and when an error is returned. Additionally, the errors should be pattern matchable.
πŸ€” 1
I need to go through The Common Lisp Condition System book: https://www.amazon.com/Common-Lisp-Condition-System-Mechanisms/dp/148426133X
Thanks for the link to Hazel @Nick Smith. That seems interesting. There's also the approach of Elixir and Erlang that take the stance that errors are unavoidable, so they provide mechanisms to recover when they do happen, including live inspection. (Which is what I understand the Common Lisp condition system to address.)
πŸ‘ 1
n
I'd love a language that specifically separated functions between those that can and cannot return errors.
Most functions beyond the identity function and constants can return errors: you simply have to pass them a value that is not part of their domain πŸ™‚. Sometimes the domain is tricky: for example the div instruction takes all numeric divisors apart from zero, and it's often hard to statically verify that zero won't be given. That's perhaps the simplest use-case for an "undefined" return value. But if a value is explicitly considered part of the domain, yet can still induce some "error-like" result (for example, failure to pass a JSON string), then I agree they should probably return a specific error value that distinguishes this class of error from the more general "I passed a string to a function expecting an integer" undefined result.
Of course, "specific error values" are implemented in modern languages as sum types, enabling pattern matching etc. But they unfortunately aren't compositional: what happens if you try and sum a "parsing error" and an "I/O error" together? That's where having a single NaN-like "undefined" value might come in handy: it just declares that an expression was given inputs outside its domain. "parsing error" + "I/O error" = "undefined"
a
Runtime errors are a fundamentally optional concept. There's not necessarily a concept of an error in a Turing machine: the closest thing is if you hit a gap in your transition table, but you can fill those in with any default that strikes your fancy. In a more conventionally shaped language, that probably means ignoring or no-opping any state or operation you don't understand.... so yeah, it looks like JavaScript, but more so. I haven't tried it, but om-language.org claims to have the property that all UTF-8 strings are valid OM programs. Unclear how it handles errors, but the words "error", "fail", "halt", and "stop" are absent from its front page.
πŸ‘ 2
For a live programming environment, rather than propagating poison values, the behavior I would rather see is this: at the first sign of trouble, save the continuation (and maybe a bit before that?) and pop up an interactive prompt to get a valid value from the user, and call the continuation with that value. (With the right UI, I think this is approximately the right model for mixing automatic and human-driven work in general)
n
I think an absent transition in a Turing machine should be considered the same as crashing: the machine cannot proceed from that point. Looking more broadly at imperative languages, it's probably only those with structured control flow can "back out" and continue after an error: just jump past/out of the current structure (block, function, etc). Turing machines don't really have a good option. I should point out though, that I was specifically thinking about declarative languages (those based on composable expressions) as I wrote my original post. I'll check out Om πŸ™‚
@Andrew F When I say "live programming" I don't mean that the user is always sitting at the computer while execution occurs, I just mean that code is immediately evaluated as it is written (and as new inputs come in), and the code doesn't "restart" unless specifically asked. I'd still want someone to be able to write a cloud service using such an environment, so interactive error resolution wouldn't be practical.
d
A language consisting only of "print" statements. And since that is the only operation, the syntax for a print statement is just the text to be printed. Each statement is on its own line. Each statement prints a new line of text. The statement to print an empty line of text, is an empty line of text. This language has well defined syntax and semantics, and every input program is valid.
πŸ€ͺ 1
πŸ‘ 1
On a more serious note, how about one that stubbornly continues to parse the current "token" or construct until it hits a well-defined delimiter? Things with special syntax take a default meaning if they don't match. For example: " starts a string, and includes anything until the next ". Invalid escaped characters fall back to their literal representation. A block starts with BEGIN and indescriminantly includes everything until an END is correctly matched. Unrecognized or unexpected tokens are simply skipped over. So assuming the closing-parenthesis is expected, A(B,C) is the same as A(B,C,+$4:"/πŸ™ƒΒΆ END {Β’=]X) Unrecognized variables become NULL (what could possibly go wrong there? πŸ˜‰)
c
There's a good talk from I think Strange Loop which is basically the system @Andrew F describes. It's a sort of infinite stack of Lisp interpreters. Whenever an unhandled error occurs, it doesn't crash, it bumps you up one meta-level and gives you a chance to "explain yourself". Unfortunately I can't remember the speaker's name so I can't find it now... I think she might actually be on the Slack.
πŸ€” 3
πŸ’‘ 1
m
I think you might be referring to this talk by Nada Amin:

https://www.youtube.com/watch?v=SrKj4hYic5A&ab_channel=StrangeLoopConferenceβ–Ύ

☝️ 3
πŸ”₯ 2
g
the behavior @Andrew F describes is pretty much exactly how smalltalk works:

https://youtu.be/eGaKZBr0ga4β–Ύ

πŸ‘ 1
h
you might also be interested in the "typed holes" concept that Darklang and others have been pushing forward, its not quite error-less but it is a lot closer to runnable-while-incomplete programs
a
Combining "no syntax errors" and "composable expressions" might be actually impossible. I'm guessing you would start by dealing with the case when you can't tell where an expression ends and the outer one it is composed with resumes. Or maybe mandatory structured editing.
n
@Harry Brundage Typed holes are interesting. Hazel (which I linked earlier) is perhaps the most interesting iteration of the idea, because it propagates erroneous subexpressions (instead of crashing), and shows you them in the output. However, I don't believe they've developed a means of programmatically recovering from (responding to) such an error. Their goal is mainly to aid code comprehension and debugging.
☝️ 1
@Andrew F Yeah for text-string languages it's probably not realistic (unless your language looks something like Dan Cook's suggestions). I am indeed focusing on structured editing in my own work.
πŸ‘ 3
d
b
@Andrew F tree languages have no such thing as syntax errors and are very composable.
n
@Dan Cook I think we have a divergence of mind here. You're referencing zany toy languages, whereas I'm interested in languages for real work πŸ˜‚
e
what about Idris? I don’t know much about it, but it seems you can treat exceptions as values http://docs.idris-lang.org/en/latest/effects/simpleeff.html#exceptions
d
@Nick Smith I wasn't just be silly for the sake of being silly, I was also making a point. What I'm saying (humor aside) is: What really is a program? What's structure? I can achieve that with exact 1:1 perfection but accomplish nothing meaningful (the print example), or have a Turing complete solution that seems like a joke on the surface, but is actually a quite practical solution (Leetcode). Of course, the first can be replaced with just print statements, and the second with just digits which is basically just a reinvention of machine code. Sounds pointless, but they fixed what's been identified as a real problem at hand. But is the problem really in the programming language, i.e. the thing with well defined and unambiguous kinds of pieces and rules for fitting them together? Or is it the medium? How about an editor where all the different parts of a program are composable like building blocks? Only things that "fit together" can go together. I think Steve Kraus did something about "Types are shapes", and I think someone else here did something about "typed holes"? Anyway, I realize I'm taking about solving the problem from there other end (and I understand the merits of textual code in "the real world" as it sits today), but I'm just offering a perspective that what if this was treated from the source rather than the sink? I guess that's the same question (and similar topic in a way) as to why people feel strongly one way or the other (or in different contexts) about static vs dynamic typing. Seriously also explore that thought-space and see what maps back to this one (i.e. only allowing valid syntax vs dealing with any syntax).
Or how about a combination of both? Maybe one thing that could mean is code in predefined structural format like Json, Xml, Yaml, S-expressions, etc. Now, those things can have syntax errors; But the goal isn't really to have exactly no syntax errors; the goal is whatever the goal of having no syntax errors is. Maybe you can get much more from reducing 90% of the syntax problem down to an extremely regular pattern, that many relatively basic editors or validators can already help deal with. Consider the trade-offs: The range of possible syntax errors becomes a very few things, and many basic tools/editors can make that very simple to deal with (and the errors are trivially obvious when pointed out); You get well defined structure that can be made very meaningful, versus perhaps sacrificing that to some parsing strategy, etc; the format lends itself well to data transfer and various other tools that need know nothing of the "language". Etc. Still I understand that this might not be the direction the discussion is aimed to go in (and thus I won't continue it further here), but in all seriousness I think it's worthwhile food for thought on the topic, at least worth a mention.