January 06, 2011
On Thu, 06 Jan 2011 10:35:07 -0500, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
> On 1/6/11 1:22 AM, Robert Jacques wrote:
>> On Mon, 03 Jan 2011 17:23:29 -0500, Adam Ruppe
>> <destructionator@gmail.com> wrote:
>>> Over the weekend, I attacked opDispatch again and found some old
>>> Variant bugs were killed. I talked about that in the Who uses D
>>> thread.
>>>
>>> Today, I couldn't resist revisiting a dynamic kind of object, and
>>> made some decent progress on it.
>>>
>>> http://arsdnet.net/dcode/dynamic.d
>>>
>>> (You can compile that; there's a main() at the bottom of that file)
>>>
>>> It isn't quite done - still needs op overloading, and probably better
>>> errors, but it basically works.
>>>
>>> It works sort of like a Javascript object.
>>>
>> [snip]
>>
>> I've been working on an update to both std.json and std.variant.
>> Previews of both are available here:
>> https://jshare.johnshopkins.edu/rjacque2/public_html/
>> though they are still works in progress. Two of the big enhancements
>> that you might be interested in are call support and opDispatch +
>> reflection + prototype structs. To paraphrase your example:
>>
>> Variant v;
>> v.a( 10 );
>> assert(v.a == 10);
>> v.a( { writefln("hello, world"); } );
>> v.a.call; //To be replaced by opCall, once struct opCall is fixed (Bug
>> 4053)
>> v.a( delegate void(string a, int x) { foreach(i;0..x) writeln(i+1,"
>> ",a); } );
>> v.a("potatoes", 3);
>>
>> I've also stubbed out a prototype style object, but I haven't really
>> tested it yet. Thoughts, comments and use/test cases are always welcomed.
>
> I think this transgresses the charter of Variant. Variant is meant to hold an object of some _preexisting_ type, not to morph into anything. We should have three abstractions:

And Variant still only holds an object of some preexisting type. What you are seeing is simply syntactic sugar for a Variant of type Variant[string]. The above lowers down into:

Variant v;
Variant[string] __temp;
v = __temp;
v["a"] = 10;
assert(v["a"] == 10);
v["a"] = { writefln("hello, world"); };
v["a"].call();
v["a"] = delegate void(string a, int x) { foreach(i;0..x) writeln(i+1," ",a); };
v["a"].call("potatoes", 3);

The only morph happens because actually making the Variant default type be Variant[string], has some issues (GC interaction, hasValue, Variant[string].init isn't usable, etc). So I decided that if and only if you used an uninitialized Variant as a Variant[string], it would 'morph' to a Variant[string].

As for the v.a -> v["a"] syntactic sugar, I have found it very useful in the parsing/use of dynamically structured structs, including JSON.

> * Algebraic holds any of a closed set of types. It should define method calls like a.fun(args) if and only if all of its possible types support the call with compatible arguments and return types.

I have considered this, but while this concept looks good on paper, in practice it cripples Algebraic. The issue is that the intersections of types tend to have no methods/operators in common. For example, Algebraic!(int,string) would have no methods nor operators defined.

> * Variant holds any of an unbounded set of types. Reflection may allow us to define v.fun(args) to look up the method name dynamically and issue a runtime error if it doesn't exist (sort of what happens now with operators).

It's not 'may' anymore. Reflection _does_ allow me to define v.fun(args) to look up the method name dynamically and issue a runtime error if it doesn't exist. eg:

    class Foo { real x = 5; }
    auto foo = new Foo;
    Variant a = foo;
    assert(a.x == 5);             // perform runtime reflection on 'a' implicitly
    a.__reflect("x",Variant(10)); // or explicitly
    assert(a.__reflect("x") == 10);	

> * Dynamic is a malleable type that you get to add state and methods to, just like in Javascript.

And I have stubbed out a Prototype object for just this reason.
January 06, 2011
On 1/6/11 11:52 AM, Robert Jacques wrote:
> And Variant still only holds an object of some preexisting type. What
> you are seeing is simply syntactic sugar for a Variant of type
> Variant[string]. The above lowers down into:
>
> Variant v;
> Variant[string] __temp;
> v = __temp;
> v["a"] = 10;
> assert(v["a"] == 10);
> v["a"] = { writefln("hello, world"); };
> v["a"].call();
> v["a"] = delegate void(string a, int x) { foreach(i;0..x) writeln(i+1,"
> ",a); };
> v["a"].call("potatoes", 3);
>
> The only morph happens because actually making the Variant default type
> be Variant[string], has some issues (GC interaction, hasValue,
> Variant[string].init isn't usable, etc). So I decided that if and only
> if you used an uninitialized Variant as a Variant[string], it would
> 'morph' to a Variant[string].

I think Variant should default to holding "void".

> As for the v.a -> v["a"] syntactic sugar, I have found it very useful in
> the parsing/use of dynamically structured structs, including JSON.

That's great, but (a) that's again outside what Variant is supposed to do, and (b) for JSON we're dealing with a closed hierarchy which suggests a different design.

>> * Algebraic holds any of a closed set of types. It should define
>> method calls like a.fun(args) if and only if all of its possible types
>> support the call with compatible arguments and return types.
>
> I have considered this, but while this concept looks good on paper, in
> practice it cripples Algebraic. The issue is that the intersections of
> types tend to have no methods/operators in common. For example,
> Algebraic!(int,string) would have no methods nor operators defined.

Algebraic with the fundamental JSON types is a great example because they all may share certain methods. Generally Algebraic with closed hierarchies (e.g. Visitor) are good candidates for the feature. Of course, if that feature affects other functionality we should avoid it. It's just "nice to have".

>> * Variant holds any of an unbounded set of types. Reflection may allow
>> us to define v.fun(args) to look up the method name dynamically and
>> issue a runtime error if it doesn't exist (sort of what happens now
>> with operators).
>
> It's not 'may' anymore. Reflection _does_ allow me to define v.fun(args)
> to look up the method name dynamically and issue a runtime error if it
> doesn't exist. eg:
>
> class Foo { real x = 5; }
> auto foo = new Foo;
> Variant a = foo;
> assert(a.x == 5); // perform runtime reflection on 'a' implicitly
> a.__reflect("x",Variant(10)); // or explicitly
> assert(a.__reflect("x") == 10);
>
>> * Dynamic is a malleable type that you get to add state and methods
>> to, just like in Javascript.
>
> And I have stubbed out a Prototype object for just this reason.

Great. Why not call it "Dynamic"?


Andrei
January 07, 2011
On Thu, 06 Jan 2011 13:24:37 -0500, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
> On 1/6/11 11:52 AM, Robert Jacques wrote:
>> And Variant still only holds an object of some preexisting type. What
>> you are seeing is simply syntactic sugar for a Variant of type
>> Variant[string]. The above lowers down into:
>>
>> Variant v;
>> Variant[string] __temp;
>> v = __temp;
>> v["a"] = 10;
>> assert(v["a"] == 10);
>> v["a"] = { writefln("hello, world"); };
>> v["a"].call();
>> v["a"] = delegate void(string a, int x) { foreach(i;0..x) writeln(i+1,"
>> ",a); };
>> v["a"].call("potatoes", 3);
>>
>> The only morph happens because actually making the Variant default type
>> be Variant[string], has some issues (GC interaction, hasValue,
>> Variant[string].init isn't usable, etc). So I decided that if and only
>> if you used an uninitialized Variant as a Variant[string], it would
>> 'morph' to a Variant[string].
>
> I think Variant should default to holding "void".

It does, with the enhancement that assignment/initialization can occur by 'member' assignment. Personally, I think assigning to a void Variant should be as permissive as possible, but this behavior is trivial to remove if needs be.

>> As for the v.a -> v["a"] syntactic sugar, I have found it very useful in
>> the parsing/use of dynamically structured structs, including JSON.
>
> That's great, but (a) that's again outside what Variant is supposed to do, and (b) for JSON we're dealing with a closed hierarchy which suggests a different design.

And JSON is supposed to be implemented _as_ a variant; it's one of the major use cases and feature litmus tests. Besides, Variant is supposed to "interfacing with scripting languages, and [allow] comfortable exploratory programming", and since this facilitates both those design goals of variant, I think this is within scope.

>>> * Algebraic holds any of a closed set of types. It should define
>>> method calls like a.fun(args) if and only if all of its possible types
>>> support the call with compatible arguments and return types.
>>
>> I have considered this, but while this concept looks good on paper, in
>> practice it cripples Algebraic. The issue is that the intersections of
>> types tend to have no methods/operators in common. For example,
>> Algebraic!(int,string) would have no methods nor operators defined.
>
> Algebraic with the fundamental JSON types is a great example because they all may share certain methods.

Except that they don't share _any_ methods. That's the point I was trying to make.

> Generally Algebraic with closed hierarchies (e.g. Visitor) are good candidates for the feature.

Since you used 'closed hierarchies' to describe JSON, what exactly do you mean by it? 'closed hierarchies' to me implies an inheritance-like relationship between, which generally means that Algebraic is the wrong choice (i.e. why not use a common interface or super-type?)

[snip]

>>> * Dynamic is a malleable type that you get to add state and methods
>>> to, just like in Javascript.
>>
>> And I have stubbed out a Prototype object for just this reason.
>
> Great. Why not call it "Dynamic"?

Because it implements prototype based programming and not dynamic programming. (http://en.wikipedia.org/wiki/Prototype_based_programming)

>
> Andrei
December 14, 2012
On Tuesday, 4 January 2011 at 16:03:28 UTC, Adam D. Ruppe wrote:
> Lutger Blijdestijn wrote:
>> The restriction with calling zero-args functions is unfortunate,
>> could this be solved by turning it into a class and dynamically
>> checking whether the type is a function and then invoke it if it is?
>
> Maybe, but I think it'd still break the property get, which
> breaks setting complex types. This, and your other concern about
> implicit declarations are actually caused by the same root
> problem: the imperfect compiler implementation of properties
> combined with variadic opDispatch templates.
>
> (Like I said in that other thread, each of those features works
> quite well on its own, but the combination reveals some minor
> bugs.)
>
>
> To set a complex type, you do the:
>
> dyn.property() = some fancy thing;
>
> Which returns a property by reference, then does its opAssign
> template, which is much more capable of fanciness than the
> runtime variadic function you get without the parenthesis.
>
> Why do that? Because overloading on property and non-property
> doesn't work right now. Through trial and error, I found
> the compiler doesn't add a property assignment's type to the
> template argument list, but does pass it as a func argument.
> (Quite possibly a (minor) compiler bug itself - I may be
> depending on one bug to work around another!)
>
> So, if you try to do a template, it will complain about wrong
> number of arguments to the function. A variadic function gets
> that error to go away and I can handle it at runtime. This comes
> with trade-offs though: the big one being that it doesn't work
> with complex types. Like the code's comment says, I really need
> the compiler's help to make them work, and it isn't around by
> the time any of that code is actually run.
>
>
> The best situation would be:
>
> @property Dynamic opDispatch(string fieldName)() // getter
> Dynamic opDispatch(string fieldName, T...)(T t) // function call,
>                                               // any num of args
> @property Dynamic opDispatch(string fieldName, T)(T t) // setter


obj.blabla();

If those are overloadable, for a "blabla" known only at runtime, would you have to send it to both the "property dispatcher" and then afterwards the "function dispatcher" if it doesn't exist as a property?

>
> Then everything would Just Work, without the limitations of
> the runtime variadic and associated workarounds. But the first
> two are broken by @property not being recognized in overloads
> currently*, and the last is broke by the right-hand side of the
> property assignment not being passed as a template argument.
>
>
> * My preferred fix here would be to give @property one more point
> in overload resolution so it is slightly preferred over non-property
> in these situations, but otherwise do nothing. It's the most
> conservative solution here. I looked at dmd's code, but I don't
> know the compiler well enough to make it happen.
>
> I guess if @property was strengthened, I could work with that too,
> just by adding an opCall and leaving opDispatch to do nothing but
> get and set. The property assign not being passed to the template
> would have to be solved somehow there too though.
>
>
>
> Anyway, none of the fixes are in place today, so I had to make
> a choice - either setting requires a named function call or
> zero arg calling requires a named function. I went with the
> latter since I figured it is a bit prettier in usage.
>
>> I would also change implicit declarations into an error, I see
>> you have an throw statement for that but commented out.
>
> Yes, the main reason is so the ref returns don't do range
> violation when you are trying to do an assignment, due to the
> above situation.
>
> It would work with simple assignment:
>
> dynobj.name; // throws
> dynobj.name = 10; // works
> dynobj.name; // still works
>
> But complex assignment won't work there:
>
> dynobj.name = someDelegate; // this won't work right, probably
> // will segfault down the line if I don't throw at runtime over it
>
> So my compromise was:
>
> dynobj.name() = someDelegate; // uses the opAssign template
>                               // which works well
>
> But... if dynobj.name didn't already exist, the left hand side
> of that would throw a range violation, getting a property that
> doesn't exist.
>
> That's why the exception is commented. If I required a .set()
> method or something like that:
>
> dynobj.set("name", whatever you want);
>
> That would work, but it no longer looks like a dynamic language...
>
>
>
> When the compiler progresses a little more (or maybe someone will
> think of a nicer workaround), we can have the best of all worlds,
> but for now this is the best I can do while keeping a mostly
> dynamic language like appearance.
>
>
> (Now, a related question here is weak typing in general. I'm
> writing this to be very weakly typed - coercing all over the place.
> Dynamic languages can throw a runtime exception on type mismatch,
> but I want to basically mimic javascript here, which does not.
> Besides, D itself is strongly typed - if you want strong types,
> just use standard D types! Possibly including std.variant.)

1 2
Next ›   Last »