Nice write-up, and good feedback.

I'd push back a little bit on the idea of "design for the call site", i.e.
that ergonomics of application code take precedence over that of the schema
language. My main concern with that notion is that schemas represent an
interface, and interfaces need to be read and understood by many more
people than implementations. For that reason I'd argue that it's important
that schemas can be expressed as clearly as possible, with the ability to
organize features semantically for presentation to a reader.

One thing that is part of that is making sure related declarations can be
grouped together, so that they can easily be read and digested together,
without scrolling back and forth. I'd say this is the main motivation for
nested declarations -- they can live next to the field or method that
references them, rather than far away at the global scope. It's also the
motivation for inlined method parameters and results, rather than requiring
separate structs.

Moreover, personally, I often write schemas as a way to organize my
thoughts and lay out a design, before I start implementing. Here, again, as
I organize my thoughts and rapidly change my mind about details, I think
it's important that I don't have to scroll up and down constantly.

That said I do sympathize with the argument that having to constantly
cross-reference other files while writing (or reading) code is painful. I
still have trouble writing a method definition without looking at the
generated header. I rely a lot of my IDE's autocomplete and
jump-to-definition to make things more bearable. 10 years ago it would have
been a lot more difficult.

----------

Regarding inheritance, I do think multiple inheritance is a must-have.
Looking at Sandstorm, there are 23 interfaces that inherit other
interfaces, and fully 16 of them are multiply-inherited. Some of them could
perhaps have been replaced by composition. For example,
VerifiedEmailSendPort could have inherited nothing and instead had two
methods, getVerifiedEmail() and getSendPort(). Arguably that's even a
better decide regardless, so that the capabilities can be passed on
separately. (Though, of course, it's always possible to accomplish the same
with membranes, albeit with higher cost.)

However, most of the multiple inheritance is to add persistence. The
`Persistent` interface is essentially a mix-in, used to add a save() method
that returns a token which can be used to get the same capability again.

https://github.com/capnproto/capnproto/blob/master/c++/src/capnp/persistent.capnp

You might argue that Persistent should also be composition-based: it could
have a method that you call to get the actual object. However, this would
mess up a lot of interface designs in Sandstorm. In lots of places, it
turns out, interfaces don't really know if the capabilities they are
passing are persistent or not. You'll see doc comments saying "this is
persistent if X and Y are true". Sometimes it's fully expected that
applications need to probe for save() support at runtime. I'd say the
underlying reason it ends up this way is because persistence is
fundamentally an orthogonal concern from business logic. Persistence is a
logistical issue. It is very natural, then, for it to be a mix-in.

You could maybe argue that persistence should be baked more directly into
the system. Maybe save() should be a method on Capability, even though not
all objects will implement it. But, that's actually how I originally
thought of persistence -- when I defined "level 2" of RPC to be
persistence, I had imagined it being baked into the protocol. I ended up
very happy that it didn't have to be; that persistence could be entirely
defined in a separate, higher layer. Ultimately I don't think there is a
single obvious design for persistence -- I think the concerns are, for
example, very different between Sandstorm apps vs, say, people talking over
the public internet. Even within Sandstorm, there are different realms of
persistence which called for slightly different interfaces
(SystemPersistent, AppPersistent, etc.). I also expect that there are
features other that persistence which might be similarly orthogonal to
business logic, which people will want the ability to mix-in in a similar
way.

So I remain pretty happy with the decision to support multiple inheritance.

-Kenton

On Mon, Jun 24, 2019 at 10:22 PM Ian Denhardt <i...@zenhack.net> wrote:

> Hey all,
>
> A few weeks ago in the thread about the Elm implementation, I mentioned
> that I had a longer critique of the schema language that I'd been
> meaning to write up. I finally got around to it; the blog post is here:
>
>
> https://zenhack.net/2019/06/25/a-critique-of-the-capnproto-schema-language.html
>
> Cheers,
>
> -Ian
>
> --
> You received this message because you are subscribed to the Google Groups
> "Cap'n Proto" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to capnproto+unsubscr...@googlegroups.com.
> Visit this group at https://groups.google.com/group/capnproto.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/capnproto/156143984544.21360.5023497358389493172%40localhost.localdomain
> .
>

-- 
You received this message because you are subscribed to the Google Groups 
"Cap'n Proto" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to capnproto+unsubscr...@googlegroups.com.
Visit this group at https://groups.google.com/group/capnproto.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/capnproto/CAJouXQnkv7TVz86_ofKcsZ%2BtVxPGVGt7tF8fcAVivSMnDqoP5Q%40mail.gmail.com.

Reply via email to