Hacker Newsnew | past | comments | ask | show | jobs | submit | sparkie's commentslogin

S-expressions are more like the tupled argument form, but better.

    (f x y z)
Is equivalent to:

    (f . (x . (y . (z . ())))
Every function takes one argument - a list.

Lists make partial application simpler than with tuples (at least Haskell style tuples), because we don't need to define a new form for each N-sized tuple. Eg, in Haskell you'd need:

    partial2 : (((a, b) -> z), a) -> (b -> z)
    partial3 : (((a, b, c) -> z), a) -> ((b, c) -> z)
    partial4 : (((a, b, c, d) -> z), a) -> ((b, c, d) -> z)
    ...
With S-expressions, we can just define a partial application which takes the first argument (the car of the original parameter list) and returns a new function taking a variadic number of arguments (the cdr of the original parameter list). Eg, using a Kernel operative:

    ($define! $partial
        ($vau (f first) env
            ($lambda rest
                (eval (list* f first rest) env))))

    ($define! f ($lambda (x y z) (+ x y z)))
    (f 3 2 1)
    => 6
    
    ($define! g ($partial f 3))
    (g 2 1)
    => 6
    
    ($define! h ($partial g 2))
    (h 1)
    => 6

    ($define! i ($partial h 1))
    (i)
    => 6
We could perhaps achieve the equivalent in Haskell explicitly with a multi-parameter typeclass and a functional dependency. Something like:

    class Partial full first rest | full first -> rest where
        partial :: (full -> z, first) -> (rest -> z)
        
    instance Partial ((a,b)) a b where
        partial (f, a) = \b -> f (a, b)
        
    instance Partial ((a, b, c)) a ((b, c)) where
        partial (f, a) = \(b, c) -> f (a, b, c)
        
    instance Partial ((a, b, c, d)) a ((b, c, d)) where
        partial (f, a) = \(b, c, d) -> f (a, b, c, d)

    ...

> Every function takes one argument - a list.

That's one way to look at it, but the major difference is that one can also pass a a list as one argument, as in `(f x y z)` and `(f (list x y z)` are not the same. The thing with tuples is that a tuple of one datum is the very same as that datum itself and the same is true with the currying situation. `(f x) y` and `f x y` are truly one and the same in Haskell and Ocaml, just as `f(x, y)` and `let val a = (x,y) in f x` are one and the same in SML. This is not the case in Rust where `f(x,y)`, a function called with two arguments, and `f((x,y))`, a function called with one argument that is a tuple of two arguments are two different things.

There is also a difference in Scheme between returning a single value that is a list containing multiple values, and actually returning multiple values, In Rust however there is no difference between returning two values and returning a pair of two values. So Rust functions actually do properly take multiple arguments but always return a single one which may or may not be a tuple. In SML, Haskell and OCaml all functions technically take only one argument and return one value.


They have dozens of passes for semantic analysis and optimization, often configurable via flags, and they target dozens of different architectures.

makes sense

(Properly formatted) XML can be parsed, and streamed, by a visibly-pushdown automaton[1][2].

"Visibly Pushdown Expressions"[3] can simplify parsing with a terse syntax styled after regular expressions, and there's an extension to SQL which can query XML documents using VPAs[4].

JSON can also be parsed and validated with visibly pushdown automata. There's an interesting project[5] which aims to automatically produce a VPA from a JSON-schema to validate documents.

In theory these should be able outperform parsers based on deterministic pushdown automata (ie, (LA)LR parsers), but they're less widely used and understood, as they're much newer than the conventional parsing techniques and absent from the popular literature (Dragon Book, EAC etc).

[1]:https://madhu.cs.illinois.edu/www07.pdf

[2]:https://www.cis.upenn.edu/~alur/Cav14.pdf

[4]:https://web.cs.ucla.edu/~zaniolo/papers/002_R13.pdf

[3]:https://homes.cs.aau.dk/~srba/courses/MCS-07/vpe.pdf

[5]:https://www.gaetanstaquet.com/ValidatingJSONDocumentsWithLea...


Without looking, I guessed that all your quotes come from academic papers. I was right.

Because real life is nothing like what is taught in CS classes.


I'm not an academic and have extensive experience with parsing.

But for whataver reason, VPAs have slipped under my radar until very recently - I only discovered them a few weeks ago and have been quite fascinated. Have been reading a lot (the citations I've given are some of my recent reading), and am currently working on a visibly pushdown parser generator. I'm more interested in the practical use than the acamedic side, but there's little resources besides academic papers for me to go off.

Thought it might be interesting to share in case others like me have missed out on VPAs.


That's not going to happen, but there's alternative research such as [1] where we get rid of the clock and use self-timed circuits.

[1]:https://arc.cecs.pdx.edu/


Advertisers will still pay, but they would need to adjust their strategy to target their ads more carefully to those who are likely to result in sales.

For legitimate senders, the attached fee would end up being spent by the receiver to send a reply, like a "refund", so it ends up zero-sum.

But for spammers it becomes an expensive option. Nobody is going to reply to the spam to "refund" their fee.


My luggage was missing when I landed at KIX.

But it wasn't the airport's fault - my luggage was still in Amsterdam.

Arrived <24 hours later and they delivered it to my hotel in Osaka.


In my experience, that's far and away the most common scenario. Luggage misses a connection, doesn't get on a flight that has ben changed because of weather, or otherwise ends up somewhere it's not supposed to be. Many airline tracking systems are better than they used to be but AirTags or equivalent are not a bad idea.


I once had SAS lose my luggage on a direct flight from Copenhagen to Tokyo Haneda. I was sure that such a thing was impossible, but I learned an important lesson that day.


Weird things do happen. I was once looking out of an airplane window waiting for takeoff when I saw my suitcase getting offloaded from the hold and taken back to the terminal. Spoke to the crew, who spoke to ground staff, who brought the bag back.

Not the slightest idea what it was all about... (I'm guessing some sort of mix-up in which they thought I'd failed to board?)


Defer might be better than nothing, but it's still a poor solution. An obvious example of a better, structural solution is C#'s `using` blocks.

    using (var resource = acquire()) {

    } // implicit resource.Dispose();
While we don't have the same simplicity in C because we don't use this "disposable" pattern, we could still perhaps learn something from syntax and use a secondary block to have scoped defers. Something like:

    using (auto resource = acquire(); free(resource)) {

    } // free(resource) call inserted here.
That's no so different to how a `for` block works:

    for (auto it = 0; it < count; it++) {

    } // automatically inserts it++; it < count; and conditional branch after secondary block of for loop.
A trivial "hack" for this kind of scoped defer would be to just wrap a for loop in a macro:

    #define using(var, acquire, release) \
        auto var = (acquire); \
        for (bool var##_once = true; var##_once; var##_once = false, (release))

    using (foo, malloc(szfoo), free(foo)) {
        using (bar, malloc(szbar), free(bar)) {
            ...
        } // free(bar) gets called here.
    } // free(foo) gets called here.


That is a different approach, but I don't think you've demonstrated why it's better. Seems like that approach forces you to introduce a new scope for every resource, which might otherwise be unnecessary:

    using (var resource1 = acquire() {
        using (var resource2 = acquire()) {
            using (var resource3 = acquire()) {
                // use resources here..
            }
        }
    }
Compared to:

    var resource1 = acquire();
    defer { release(resource1); }
    var resource2 = acquire();
    defer { release(resource2); }
    var resource3 = acquire();
    defer { release(resource3); }
    // use resources here
Of course if you want the extra scopes (for whatever reason), you can still do that with defer, you're just not forced to.


While the macro version doesn't permit this, if it were built-in syntax (as in C#) we can write something like:

    using (auto res1 = acquire1(); free(res1))
    using (auto res2 = acquire2(); free(res2))
    using (auto res3 = acquire3(); free(res3)) 
    {
        // use resources here
    } 
    // free(res3); free(res2); free(res1); called in that order.
The argument for this approach is it is structural. `defer` statements are not structural control flow: They're `goto` or `comefrom` in disguise.

---

Even if we didn't want to introduce new scope, we could have something like F#'s `use`[1], which makes the resource available until the end of the scope it was introduced.

    use auto res1 = acquire1() defer { free(res1); };
    use auto res2 = acquire2() defer { free(res2); };
    use auto res3 = acquire3() defer { free(res3); };
    // use resources here

In either case (using or use-defer), the acquisition and release are coupled together in the code. With `defer` statements they're scattered as separate statements. The main argument for `defer` is to keep the acquisition and release of resources together in code, but defer statements fail at doing that.

[1]:https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...


Defer is a restricted form of COMEFROM with automatic labels. You COMEFROM the end of the next `defer` block in the same scope, or from the end of the function (before `return`) if there is no more `defer`. The order of execution of defer-blocks is backwards (bottom-to-top) rather than the typical top-to-bottom.

    puts("foo");
    defer { puts("bar"); }
    puts("baz");
    defer { puts("qux"); }
    puts("corge");
    return;
Will evaluate:

    puts("foo");
    puts("baz");
    puts("corge");
    puts("qux");
    puts("bar");
    return;


That is the most cursed description I have seen on how defer works. Ever.


This is how it would look with explicit labels and comefrom:

    puts("foo");
    before_defer0:
    comefrom after_defer1;
    puts("bar");
    after_defer0:
    comefrom before_defer0;
    puts("baz");
    before_defer1:
    comefrom before_ret;
    puts("qux");
    after_defer1:
    comefrom before_defer1;
    puts("corge");
    before_ret:
    comefrom after_defer0;
    return;
---

`defer` is obviously not implemented in this way, it will re-order the code to flow top-to-bottom and have fewer branches, but the control flow is effectively the same thing.

In theory a compiler could implement `comefrom` by re-ordering the basic blocks like `defer` does, so that the actual runtime evaluation of code is still top-to-bottom.


Compile using `-fkeep-inline-functions`.


Doesn't help. The point is to avoid having to invoke a C compiler when working in X language. But it would certainly be nice if the distros enabled that.


`inline` is a hint, but he declares `static_inline` in the preprocessor to include `__attribute__((__always_inline__))`, which is more than just a hint. However, even `always_inline` may be troublesome over translation units, though we can still inline things in different translation units if using `-flto`, I believe there are occasional bugs. For libraries we'd also want to use `-ffat-lto-objects`.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: