[HN Gopher] Hurl: A terrible but cute idea for a language
       ___________________________________________________________________
        
       Hurl: A terrible but cute idea for a language
        
       Author : todsacerdoti
       Score  : 139 points
       Date   : 2023-06-19 17:13 UTC (5 hours ago)
        
 (HTM) web link (ntietz.com)
 (TXT) w3m dump (ntietz.com)
        
       | smackjer wrote:
       | I think I'm gonna hurl!
        
       | [deleted]
        
       | scrame wrote:
       | > Oh, also, functions cannot be recursive (without passing in a
       | function to itself), because we won't have the function bound to
       | a name in the local context when defining itself.
       | 
       | OP Might want to look into the origins of this website name.
        
       | bfung wrote:
       | Fun experiment, but feels like a miss not to then explore the
       | Optional type ideas and eliminate 'catch' all together.
        
       | DaveFlater wrote:
       | Deja vu, I have been in this place before. Prior art: John Aycock
       | and Mike Zastre. An Exceptional Programming Language. Proceedings
       | of the 2005 International Conference on Programming Languages and
       | Compilers.
       | https://pages.cpsc.ucalgary.ca/~aycock/papers/plc05.pdf
        
       | shaftoe444 wrote:
       | I love it.
        
       | yccs27 wrote:
       | Does it really need the toss/return syntax to loop? You could use
       | let loop = func(state, self) {             let next = foo(state);
       | self(foo);         };         loop(start, loop);
        
       | amluto wrote:
       | > You can toss it, which works a little differently: it traverses
       | the stack until you reach a matching catch block, but then you
       | can use the return keyword to go back to where the value was
       | tossed from. > > I know, it's cursed using return in this unusual
       | way. Again, sorry, I didn't make you keep reading. But, the
       | reward is that since you got here, you get to see how we can use
       | these to create control flow.
       | 
       | Maybe calling it 'return' is cursed, but this concept isn't
       | cursed at all. These are resumable exceptions, and I used to love
       | them! Windows supports them natively, although I don't think
       | they're widely used. Here's the C/C++ extension for Window's
       | native "structured exception handling", and ordinary C++
       | exceptions are built on top of it:
       | 
       | https://learn.microsoft.com/en-us/cpp/cpp/structured-excepti...
       | 
       | It supports:
       | 
       | > Recognize the exception but dismiss it
       | (EXCEPTION_CONTINUE_EXECUTION).
       | 
       | And as a kernel programmer, I really like resumable exceptions --
       | most _hardware_ exceptions are resumable, and the kernel uses
       | them extensively. When you get a page fault, that 's an
       | exception, and the kernel has a handler, and the control flow,
       | um, yeets to the handler, and the handler, well, returns (or
       | exits or whatever you want to call it) right back to the
       | exception site.
       | 
       | And POSIX signals support this too -- it's even the default
       | behavior when a signal handler returns. (POSIX signals suck.)
        
       | lnenad wrote:
       | FYI Bitlocker shows ntietz as a virus and won't let me access it.
        
         | ntietz wrote:
         | Thanks for letting me know! I couldn't find anything about
         | Bitlocker as antivirus but assuming you're talking about
         | BitDefender (which also has flagged it). I've submitted a false
         | positive report.
        
           | LinuxBender wrote:
           | It looks like a few vendors are flagging the URL. [1] Not
           | sure why. Nothing sticks out at me on urlscan [2] You may
           | have to reach out to those vendors to see what is tickling
           | their detectors.
           | 
           | [1] - https://www.virustotal.com/gui/url/d4deec9bca49cdedb57d
           | 059d9...
           | 
           | [2] - https://urlscan.io/result/157fd01c-3442-43ae-a563-77ac7
           | af2dc...
        
             | ntietz wrote:
             | Hmm interesting, if anyone can help me track down why I
             | would greatly appreciate it!
        
               | firstlink wrote:
               | I trust you already obtained the proper licenses from
               | your local authorities before designing and implementing
               | a new programming language? And those licenses are on
               | file with the major security vendors?
        
               | alexisread wrote:
               | I did find the licences in the bottom of a locked filing
               | cabinet stuck in a disused lavatory with a sign on the
               | door saying 'Beware of the Leopard.
        
       | cogman10 wrote:
       | If you squint a little, what OP has invented here is pattern
       | matching with algebraic data types.
        
       | remram wrote:
       | The cute idea is:
       | 
       | > the only control flow you get is error handling
       | 
       | (and function definition/call, just without returns)
        
       | Joker_vD wrote:
       | Or you can go all the way in the other direction (double- or
       | triple-barreled CPS) and have functions that can't return but
       | only call other functions.                   let fizzbuzz =
       | func(ret, fizzbuzz, x, max) {             (x == max)(ret, func()
       | {                 let printed = false;                 (x %
       | 3)(func (t0) {                     let k0 = func() {
       | (x % 5)(func (t1) {                             let k1 = func() {
       | let k2 = func() {                                     (x +
       | 1)(func (t2) {
       | fizzbuzz(ret, fizzbuzz, t2, max);
       | })                                 };
       | (printed == false)(func () {
       | print(k2, x);                                 }, k2);
       | };                                      (t1 == 0)(func () {
       | print(func() {                                     printed =
       | true;                                     k1();
       | }, "buzz");                             }, k1);
       | });                     };                              (t0 ==
       | 0)(func() {                         print(func() {
       | printed = true;                             k0();
       | }, "fizz");                     }, k0);                  });
       | });         }                  fizzbuzz($halt, fizzbuzz, 0, 100);
        
         | abecedarius wrote:
         | Raph Levien's Io was along those lines (but conciser).
         | (Different language than a newer now better-known Io.)
        
         | quickthrower2 wrote:
         | Is this a slightly less esoteric version of the Lambda
         | calculus?
        
       | camdenlock wrote:
       | Irrelevant, but: trans hands clearly made this. Interesting how
       | obvious it has become.
       | 
       | (Also, I'm trans, so according to The Rules, you're not allowed
       | to get cranky at me for writing this post. Yes, even you there.
       | Stop it.)
        
       | debarshri wrote:
       | Not to confuse with Hurl.dev[1], which is a net request
       | automation tool
       | 
       | [1]https://hurl.dev/
        
         | lfconsult wrote:
         | Right.
        
       | kabdib wrote:
       | The Icon language (by the Griswalds, the same couple who brought
       | us SNOBOL of decades past) has similar control structures for its
       | generators.
        
       | chomp5977 wrote:
       | [flagged]
        
       | dcsommer wrote:
       | I love this exercise. Great job exploring a concept fully. I find
       | this article one of the better arguments I've seen yet against
       | including exceptions in a language.
        
       | duped wrote:
       | While cute this is really close to call/cc and/or algebraic
       | effects as an abstraction for control flow, which isn't as much
       | "cute" as it is "a powerful way to express programs in as few
       | expressions as possible"
        
         | btown wrote:
         | Yep! Everyone should read https://overreacted.io/algebraic-
         | effects-for-the-rest-of-us/ for how powerful this can be in
         | practice. The caller gets to pause execution on the
         | raising/hurling of an event - which makes many crazy things
         | possible.
        
           | compscigirl wrote:
           | I feel like I must be missing something. It seems to me (just
           | from reading this post) that if you need algebraic effects
           | you've probably painted yourself into a corner and should
           | probably reconsider your program's architecture. It seems
           | like a good way obscure what the code is really doing and end
           | up with poorly organised spaghetti code. In the post he's
           | effectively making a function async while pretending it's
           | not, or calling a callback but making the top level code's
           | problem instead of encapsulating in the associated type. I'm
           | sure there's uses for algebraic effects down the line I
           | haven't thought of, but I don't understand why these would be
           | desired outcomes?
        
             | duped wrote:
             | Look at it less like a first class language feature and
             | more like a language feature for implementing others in
             | terms of it.
        
             | dvt wrote:
             | You're not missing anything. Algebraic effects are
             | basically typed gotos+[1][2], and are generally a bad idea
             | to use and/or implement. They can be useful in a few very
             | niche situations when using typed functional languages.
             | 
             | + With added asynchronicity.
             | 
             | [1] http://community.schemewiki.org/?call-with-current-
             | continuat...
             | 
             | [2] Some argue call/cc is, in fact, _worse_ than goto:
             | https://okmij.org/ftp/continuations/against-callcc.html
        
               | habitue wrote:
               | loops are typed gotos, so are if statements, so, really
               | are functions.
               | 
               | Goto isn't bad, it's integral to programming. It's just
               | too powerful, so we tame it in various ways. Algebraic
               | effects are one of those ways.
        
               | dvt wrote:
               | > loops are typed gotos, so are if statements, so, really
               | are functions.
               | 
               | All of the above don't break the continuity of a
               | program's control flow. Maybe you should be arguing with
               | Dijkstra, not me: https://www.cs.utexas.edu/users/EWD/tra
               | nscriptions/EWD02xx/E...
               | 
               | Pretty sure we've already litigated that goto/jmp is bad,
               | which is why it's almost never used in modern C/C++ code
               | unless doing very specific things.
        
         | smt88 wrote:
         | > _express programs in as few expressions as possible_
         | 
         | I don't find this appealing on its face. Extreme terseness,
         | even when done very elegantly, makes programs very hard to read
         | and sometimes also hard to maintain.
         | 
         | I suspect that's one of the reasons Lisps (and functional
         | languages in general) haven't caught up in popularity even as
         | it becomes much easier to adopt one for any target and in any
         | organization.
        
           | Yoric wrote:
           | > I don't find this appealing on its face. Extreme terseness,
           | even when done very elegantly, makes programs very hard to
           | read and sometimes also hard to maintain.
           | 
           | In that case, it's more about minimizing the language, rather
           | than the programs. call/cc is a single instruction that is
           | sufficiently expressive to implement e.g. exceptions,
           | coroutines and lots of stuff for which people typically use
           | monad-style embeddings.
           | 
           | Making the language easier to specify is generally a good
           | thing, because it makes all forms of static analysis and
           | compilation easier (at least theoretically) and because there
           | are fewer places to hide bugs in the compiler/interpreter. Of
           | course, you move the potential bugs to the libraries, but
           | that's generally considered better, because it's easier to
           | debug.
           | 
           | > I suspect that's one of the reasons Lisps (and functional
           | languages in general) haven't caught up in popularity even as
           | it becomes much easier to adopt one for any target and in any
           | organization.
           | 
           | Well, to be fair, functional constructs have made it to
           | pretty much all mainstream languages these days.
        
             | andyferris wrote:
             | I believe the point was you can say the same about goto.
             | With goto you can replace if/else, for loops, while loops,
             | functions, exceptions, etc, making for a very minimal
             | language. Yet structured programming is superior.
             | 
             | (I probably don't know enough about call/cc to know if
             | that's a totally fair comparison, but languages being more
             | restrictive/less flexible can be good overall in terms of
             | aiding understanding/reducing bugs/etc)
        
             | ekidd wrote:
             | > _call /cc is a single instruction that is sufficiently
             | expressive to implement e.g. exceptions, coroutines and
             | lots of stuff for which people typically use monad-style
             | embeddings._
             | 
             | The flip side of this is that call/cc prevents you from
             | relying on a stack, except in cases where you can do heavy
             | static analysis. And you virtually never need the full
             | power of call/cc unless you're implementing coroutines.
             | 
             | This is the main reason that so few languages support
             | call/cc: stacks are a nice implementation technique (as
             | opposed to GCed activation records on the heap), and
             | call/cc comes with a heavy price for very situational
             | value.
             | 
             | One alternative are "escape" continuations, which can only
             | be used during the lifetime of the creating block. These
             | are stack friendly.
        
           | duped wrote:
           | > Extreme terseness, even when done very elegantly, makes
           | programs very hard to read and sometimes also hard to
           | maintain.
           | 
           | The advantage to something like call/cc is not that you can
           | make code extremely terse but rather that there is one code
           | path for analyzing control flow and each variant of control
           | flow isn't a special case. It's also not more terse at all,
           | but rather more explicit.
           | 
           | You don't need to force users to use it, but it is useful to
           | define more useful mechanisms like if/else if, match/switch,
           | throw/catch/finally, coroutines, etc in terms of an
           | abstraction that doesn't break type checking or
           | codegeneration.
           | 
           | All that said "call/cc considered harmful" is an old take
        
         | nerdponx wrote:
         | Generalized exception-throwing is pretty much the Common Lisp
         | condition system.
        
       | lpghatguy wrote:
       | This is incredibly funny and also brilliant: toss/return is an
       | implementation of algebraic effects!
        
         | gpderetta wrote:
         | call/cc as the _only_ control flow primitive!
        
           | firstlink wrote:
           | Algebraic effects use delimited continuations (and this
           | appears to match toss/return). Call/cc captures an
           | undelimited continuation. Totally different.
        
       | juujian wrote:
       | "Hurl" is terribly cute, but I suspect you could be even more
       | successful if you had called it "yeet" :)
        
         | Deukhoofd wrote:
         | Rust already uses that for a similar feature.
         | 
         | https://doc.rust-lang.org/beta/unstable-book/language-featur...
        
         | Quarrelsome wrote:
         | hurl has the enjoyable duality where it can mean throw or
         | vomit, where in the case of this language's curious
         | construction both meanings apply.
        
           | ntietz wrote:
           | This was a consideration in naming the language
        
             | Quarrelsome wrote:
             | it is most admirable naming <3.
        
       | SashaSirotkin wrote:
       | This is very fun!
       | 
       | I am only making suggestion because it rhymes with "hurl" (and it
       | works well metaphorically for the repeating tossing/hurling): you
       | could introduce a "whirl" keyword or part of the standard library
       | as the way to handle loops.
       | 
       | It could be functionally the same as your loop example but
       | removes the self-referential first argument (i.e. whirl(count,
       | [1,3])).
        
         | thekiptxt wrote:
         | In my university, the slang term for an uninitialized variable
         | was "dead squirrel". IIRC, some TA had described uninitialized
         | C++ variables as follows: "It could be any value: zero, 100, or
         | even a dead squirrel." If hurl ever allows uninitialized
         | variables, I would love to see use of an uninitialized variable
         | to `hurl` a `squirrel`! :)
        
       | SAI_Peregrinus wrote:
       | You can make it more terrible (but still Turing complete) by
       | removing `toss` and `return`, since you have conditional catches.
       | Just nest enough catches so that when you throw the execution
       | will start again and skip over any unnecessary code.
        
       | knome wrote:
       | Your 'toss' primitive looks like common lisp's condition+restart,
       | using a catch-block syntax in place of a `handler-bind` function.
       | 
       | https://stackoverflow.com/questions/36178141/common-lisp-exc...
        
       | [deleted]
        
       | gmalette wrote:
       | A few years back I wrote a language called "exceptional" based on
       | some shared ideas. Hurl is super neat and you took things further
       | than I did.
       | 
       | https://github.com/exceptional-lang/exceptional
        
       | andai wrote:
       | To be fair, you did warn me!
        
       | alexisread wrote:
       | Exceptions as the underpinning of such a lang is interesting, but
       | I think you might be better off with push-prompt, and an
       | implementation of delimited dynamic binding to allow eg. Process
       | migration.
       | 
       | Wat is a very small language which does fexprs (vau calculus),
       | delimited continuations, delimited dynamic binding, custom
       | algebraic effects (try catch, fibres) on top of them, modules,
       | types, and has a metacircular VM in a few hundred LOC. It's
       | missing a native implementation but it's very impressive.
       | 
       | https://github.com/GiacomoCau/wat-js
       | 
       | Links to the formal research are in the readme.
       | 
       | http://axisofeval.blogspot.com/?m=1 Has several good discussions
       | on the topics, and are more accessible than the papers.
        
       | tshaddox wrote:
       | > Oh, also, functions cannot be recursive (without passing in a
       | function to itself), because we won't have the function bound to
       | a name in the local context when defining itself. Fun, right?
       | 
       | The startup accelerator that runs this forum is named after a
       | pretty neat thing you can do with a language that only has
       | anonymous functions:
       | 
       | https://en.wikipedia.org/wiki/Fixed-point_combinator
        
         | Joker_vD wrote:
         | You don't even need that, there is a much more efficient way in
         | a language that supports let-bindings (but not letrec):
         | let f' = func(f, g, other_args...) {             // ...
         | g(f, g, whatever...);             // ...         };
         | let g' = func(f, g, other_args...) {             // ...
         | f(f, g, whatever...);             // ...         }
         | let f = func(other_args...) { return f'(f', g', other_args...);
         | }         let g = func(other_args...) { return g'(f', g',
         | other_args...); }
         | 
         | That's basically just closure conversion, only instead of doing
         | it the compiler backend, you do it manually yourself.
        
       ___________________________________________________________________
       (page generated 2023-06-19 23:00 UTC)