Re: JSON-LD API w/DOM Futures implemented

On 04/28/2013 05:19 AM, Markus Lanthaler wrote:
> On Sunday, April 28, 2013 3:21 AM, Dave Longley wrote:
>> On 04/27/2013 10:59 AM, Markus Lanthaler wrote:
>>> Good that you mention C++. If you define a function that has a
>>> *non*-optional parameter and try to call it without passing any
>>> parameter in a statically typed and compiled language your compiler
>>> will simply refuse to compile your code. The reason is that you tried
>>> to call a non-existent function. The compiler can't find any function
>>> with matches the signature you used.
>> When you call functions in JavaScript, you don't call *directly* into
>> a corresponding C++ function, rather your script is JIT-compiled (or
>> interpreted). This isn't different for a JavaScript function that has
>> a native implementation.
> But I wasn't talking about JavaScript at all. I was talking about C++. About
> statically typed and compiled languages. WebIDL is not JavaScriptIDL. It is
> a generic interface description language. It has a ECMAScript binding but
> that doesn't change the fact that it is intended to be generic.

Well, WebIDL is about describing interfaces that are to be implemented 
in browsers:

"This document defines an interface definition language, Web IDL, that 
can be used to describe interfaces that are intended to be implemented 
in web browsers."

http://www.w3.org/TR/WebIDL/

I think this is really the only case we need to be concerned about -- 
and those that argued against introducing a node.js-style callback 
mechanism made the same argument.

>>> So this has nothing to do with error propagation or wrapping existing
>>> APIs. It is just a result of JavaScript being so lazy (or tolerant,
>>> depending on how you look at it). WebIDL clearly differentiates
>>> between optional and non-optional parameters. Obviously you can't omit
>>> a non-optional parameter.
>>>
>>> The thing where we seem to disagree is whether the function is called
>>> at all or not. If I'm not completely misunderstanding you (and I doubt
>>> I am), you are saying that the function will be called even if the
>>> signature used by the caller doesn't correspond to the signature
>>> definition of the method. I'm arguing that the function is not called
>>> at all and thus an error is raised directly instead of being returned
>>> via the Future.
>> It's a question of how functions that are "called in the Future" are
>> *meant* to be understood.
> The methods we are talking about are not called in the Future they are
> called instantaneously. It is the return value that is returned in the
> Future.
>
>
>> Does returning a Future from such a method
>> *mean* that you use the Future to invoke the function later -- and
>> thus, you haven't *actually* invoked anything yet so there should be
>> no runtime errors?
> You have invoked the function. It returns a Future. You don't know when the
> Future will be resolved. The point is that all the complex operations to
> resolve the Future are done asynchronously. That doesn't change the fact
> that the method call itself is synchronous.
>
>
>> In other words, is the constructor for the Future
>> an implementation detail that should be effectively abstracted away?
>> Or should we be well-aware that there's a constructor that may be
>> doing something more than just creating a way to invoke a function in
>> the Future? Make sense?
> I don't understand this paragraph. The Future constructor doesn't create "a
> way to invoke a function in the future". It creates a placeholder object for
> a return value that is not ready yet to be returned. At some point in the
> future that return value will be available and you can use it. You can
> register a callback to be notified when that happens.

You're talking about implementation details, not *meaning*.

>> If the error is normally generated when you invoke the function, and,
>> by using Futures we *mean* for a function to not actually be invoked
>> until we try to resolve a Future, isn't it odd to throw any sort of
>> exception related to its invocation before it is actually invoked?
> No, because we (well, at least I) don't mean "for a function to not actually
> be invoked until we try to resolve a Future". We don't try to resolve a
> Future. The method passed to the Future constructor resolves the Future. We
> just register a callback to be notified when that happens.
>
> I think you are interpreting too much into Futures. Let's go back to the
> original definition (and make the callback optional):
>
>    void expand (JsonLdInput input, optional JsonLdCallback callback);
>
> What should happen if I call this method without any parameters, expand()?
> It should throw an error, right? I have to pass an input.
>
> So let's now assume we change the return value from void to CallbackRegistry
> and remove the callback parameter (it was optional anyway), nothing else
> will be changed, the callback is just never called.
>
>    CallbackRegistry expand (JsonLdInput input);
>
> What should happen if I call this method without any parameters? I think we
> agree it should still throw an error, right?
>
> What if we now say that instead of calling the callback that has been passed
> directly in the first example we allow the caller to register the callback
> using the CallbackRegistry?
>
>    var cbRegistry = expand (JsonLdInput input);
>    cb.registerCallback(myCallback);
>
> It would still throw an error if I call expand(), right?
>
> Finally, we decide to do some refactoring and rename some things. Our
> definition would end up looking as follows:
>
>    Future expand (JsonLdInput input);
>
> I would also need to change my code slightly because the "registerCallback"
> method was renamed to "done":
>
>    var future = expand (JsonLdInput input);
>    future.done(myCallback);
>
> Would calling expand() without any parameters still throw an error? Yes, why
> not? Do we have Futures now, yes.
>
> I hope this clarifies how I see things. I would love to see why you think
> that the last code snipped shouldn't throw an error.

I haven't misunderstood you from the beginning (I don't think). But none 
of this addresses my point. It is all about implementation details, not 
the *meaning* of Futures -- and how they relate, consistently, to an 
analogy to synchronous programming without being synchronous at all 
themselves. If we were to just look at implementation details without 
understanding the higher-level abstraction of what Futures are supposed 
to *mean*, I would agree with everything you've said. But there is an 
emergent property from all of this; it isn't just about renaming symbols 
with different names.

I don't really care how all of this turns out, to be honest, but I feel 
as though various arguments made with respect to the way Futures behave 
(they must be near-perfect analogies to synchronous programming, etc.) 
would be inconsistent if we don't think of these asynchronous operations 
as actually fully happening in the future, rather than partially in the 
present.

It's like there is a gatekeeper getting in the way of making 
asynchronous calls here, that isn't there in the synchronous case. 
Here's the difference to the two error-raising approaches:

Dev: I'd like to be able to call X in the future (with these parameters).
App: Ok. Here's something to let you do that. Now you can call X later.
*Dev attempts to call X*
X: Oh, there's a problem.

vs.

Dev: I'd like to call X in the future (with these parameters).
App: No, I already know what would happen in the future if you tried to 
do that with X, despite the fact that (perhaps) it's not my 
responsibility to know that. I won't even allow you to call X and find 
out for yourself.

As you can see, the root of this problem may be that the parameters are 
being leaked due to implementation details. If the thing responsible for 
making it possible for me to call a method in the future wasn't able to 
see those parameters then this problem would entirely go away. It's just 
an implementation detail (that essentially presumes closures of some 
sort will be used) that enables this problem to even exist. This also 
reflects the problem that, if X can be called in more than one way, now 
both X and a gatekeeper have to know what conditions make that permissible.

If you changed the Futures API to work like this:

Future createFutureFor(asyncOp);

var foo = createFutureFor(expand);
var x = yield foo(input);

You could see how the constructor "createFutureFor" has nothing to do 
with the parameters given to the asynchronous operation.

>
>
> Cheers,
> Markus
>
>
> --
> Markus Lanthaler
> @markuslanthaler
>
>


-- 
Dave Longley
CTO
Digital Bazaar, Inc.

Received on Sunday, 28 April 2013 17:10:25 UTC