[e-lang] How to fix the Data-E unserialization order problem
Kevin Reid
kpreid at mac.com
Thu Apr 19 01:15:33 CDT 2007
How to fix Data-E:
The problem with Data-E is that serialization may create an ordering
of calls which does not succeed when executed/unserialized: in
particular, x.y() might be executed before x is resolved.
In order to fix this robustly, we need to, at unserialization time,
perform the calls in an ordering which respects dependencies;
recipients must be unserialized before calls to them, and some calls
may require that some arguments are unserialized before those calls
occur (e.g. making a ConstSet).
We would like to preserve the property that the serialized form is
interpretable as a subset of E.
My thought: An E programmer might want, if they can express it
reasonably cleanly, the same behavior of "assemble these objects in
whichever order will succeed" that Data-E needs; it *might* enable
the writing of more declarative E programs.
Whether or not this is actually the case, it suggests a way to fix
Data-E:
1. Write a library providing the operation of performing a set of
calls in an appropriate order.
2. Redefine Data-E as the subset of E which expresses usage of
this library, which I'll call the "assembler".
The non-E (binary, AST, ...) forms for Data-E remain essentially as
they are, except they cease being nested, and are considered to refer
to the library; the E-expression form becomes E programs which use
the assembler.
Data-E continues to be a subset of E; but instead of being "only
LiteralExpr, CallExpr, DefineExpr ..." it is "only these patterns of
calls to an object providing the assembler protocol".
MarkM and I just finished discussing this idea, and at the moment we
think it is an adequate solution to the Data-E problem (provided that
we can design a suitable assembler, of course).
The remaining problem is: What is the protocol for the assembler?
A very simple answer:
def [finish, o1, o2, _] := assemble([
[o2, "getSomeFacet", []],
[makeThingy, "run", [o3]],
[__makeList, "run", [0, 0, 17, 5, 0]]
])
finish()
o1
This would be adequate for Data-E, but it is unpleasant for
programmer usage; furthermore, it generates a large list of promises
which is not used other than for variable binding.
Eliminating the binding list and finish():
def o2Resolver := (def o2)
def o3Resolver := (def o3)
assemble([
[def o1, o2, "getSomeFacet", []],
[o2Resolver, makeThingy, "run", [o3]],
[o3Resolver, __makeList, "run", [0, 0, 17, 5, 0]]
])
o1
This is simple, but requires either pre-declaring promises and
resolvers or the entries to be ordered (when possible) such that uses
occur after declarations, defeating one of the motivations for the
assembler.
Stateful assembler, accumulating a record of what it needs to deliver:
def o3
def [o1, o2] := [assemble(o2, "getSomeFacet", []),
assemble(makeThingy, "run", [o3])]
bind o3 := assemble(__makeList, "run", [0, 0, 17, 5, 0])
assemble.finish()
As in this contrived example, this allows the user to use any normal
definition technique (recursive 'def', forward declaration) to
construct the loops.
Now about the call syntax. Here's a variation on the stateful assembler:
def o3
def [o1, o2] := [assemble(o2).getSomeFacet(),
assemble(makeThingy).run(o3)]
bind o3 := assemble(__makeList).run(0, 0, 17, 5, 0)
assemble.finish()
The trick here is that assemble#run/1 takes the recipient promise and
returns a plumbing object[1], which takes a call to it, no matter
what the verb, as the message to deliver to the recipient when it is
resolved. The intended advantage of this is that normal E call syntax
is used for the verb and arguments.
At this point, I now realize, we actually have something which could
be generated in the same shape as current Data-E, with added
'assemble' calls, and succeed where current Data-E fails. I'll post a
definition of the assembler tomorrow.
--
Kevin Reid <http://homepage.mac.com/kpreid/>
More information about the e-lang
mailing list