Parsing Json as polymorphic value and should yeti.lang.GenericStruct also implement yeti.lang.Coll and ByKey?

107 views
Skip to first unread message

chrisichris

unread,
Jan 31, 2012, 5:44:00 AM1/31/12
to yeti-lang
Hi,

Fist I'd like to ask you what you think of parsing JSON to a
polymophic value:

parseJson jsonString is string -> 'a =
(obj is ~Object = //parse jsonString to yeti.lang.GenericStruct;
obj unsafely_as 'a);

//than it can be used like any other yeti value on the repl

> rs = parseJson '{"name":"yeti", "version":1}'
rs is 'a = {name="yeti", version=1}
> rs.name
"yeti" is 'a
> rs
{name="yeti", version=1} is 'a
> rs.version + 2
3 is number
> rs.nofield //returns null
[] is 'a

This way I can parse Json conviniently like in dynamicly typed
language, still keeping compile-time typesafety for all the rest of
the code (and with parsed Json you get anyway only at runtime)

So my fist question is what do you think of this approach?

My second question: Would it be possible that yeti.lang.GenericStruct
implements Coll and ByKey, because the current only problem with this
approach is that parse time I do not know wheter to parse a Json-
Object to a GenericStruct or a Hash even so both at rutnime IMO are
quite the same map<string,~Object>

Christian

Madis

unread,
Feb 1, 2012, 6:11:44 PM2/1/12
to yeti-lang

On Tue, 31 Jan 2012, chrisichris wrote:

> Fist I'd like to ask you what you think of parsing JSON to a
> polymophic value:
>
> parseJson jsonString is string -> 'a =
> (obj is ~Object = //parse jsonString to yeti.lang.GenericStruct;
> obj unsafely_as 'a);

Quite obviouly unsafe, the type of 'a isn't checked any way, and if the
string doesn't happen to be in expected way, using the value will blow up
somewhere.

> //than it can be used like any other yeti value on the repl
>
>> rs = parseJson '{"name":"yeti", "version":1}'
> rs is 'a = {name="yeti", version=1}
>> rs.name
> "yeti" is 'a
>> rs
> {name="yeti", version=1} is 'a
>> rs.version + 2
> 3 is number
>> rs.nofield //returns null
> [] is 'a
>
> This way I can parse Json conviniently like in dynamicly typed
> language, still keeping compile-time typesafety for all the rest of
> the code (and with parsed Json you get anyway only at runtime)
>
> So my fist question is what do you think of this approach?

That some compiler extension should be implemented. ;)
Like the one vaguely described in doc/stmap.yeti in git (structOf is a
magic keyword there).

The problem here is that yes, with JSON you'll get error somewhere, but
the right place to get it would be when parsing it, not somewhere in the
code using the parse result.

> My second question: Would it be possible that yeti.lang.GenericStruct
> implements Coll and ByKey, because the current only problem with this
> approach is that parse time I do not know wheter to parse a Json-
> Object to a GenericStruct or a Hash even so both at rutnime IMO are
> quite the same map<string,~Object>

The GenericStruct isn't really map<string, ~Object>. Sure, it uses String
instances as keys, but like all Struct implementations, it does String
object identity checking (java ==), and not value comparision.

From the Yeti typesystem perspective it implements entirely different
type, but that's another topic.

With current approach, only type that would make sense to me would look
like this:
----
typedef json = Str string | Num number | List list<json> | Struct map<string, json>
----
Or this using accessor fields:
----
class JsonError(String error) extends Exception(error) end

typedef json = {
/// throws JsonError if not number
num is number,
str is string,
list is list<json>,
/// throws JsonError if not JSON object
obj is map<string, json>
}

rs = parseJson '{"name":"yeti", "version":1}'

// this throws JsonError if the input wasn't an expected one
println "version: \(rs.obj.['version'].num)";
----

Because well, the parseJson don't know the format of the expected Json.
With the structOf or some another language extension it could be possible
to describe the expected format to parse function, solving the issue.

chrisichris

unread,
Feb 2, 2012, 1:58:42 AM2/2/12
to yeti-lang
> The problem here is that yes, with JSON you'll get error somewhere, but
> the right place to get it would be when parsing it, not somewhere in the
> code using the parse result.
>

Sure it is better practice to get the runtime error where you parse
the json, but IMO as the compiler can not gurantee compile time
typesafety it should be handled like in dynamic langauges and left to
the programmer where he wants to test for it for prototyping or ie a
rest-services which is direclty rendered to html, having the parse
time extra check does not use much there.

But even the parse-time checking seems simpler: ie if you have a
service which returns a csutomer struct:
o = parseToGenericStruct jsonString
customer = {
name = o.name ^ "", //testing for string
turnover = o.turnover + 0,
remark = maybeDefined \none (Some . (^ "")) o.remark,
}

this is much shorter than any other aproach I have tried so far

> The GenericStruct isn't really map<string, ~Object>. Sure, it uses String
> instances as keys, but like all Struct implementations, it does String
> object identity checking (java ==), and not value comparision.
>

I have extends yeti.lang.Hash now to implement the Struct interface.
Does this work? Because Hash uses equals and I guess there should be
no problem regarding identity two ident strings are of course equals
as well.


> class JsonError(String error) extends Exception(error) end
>
> typedef json = {
>         /// throws JsonError if not number
>         num is number,
>         str is string,
>          list is list<json>,
>          /// throws JsonError if not JSON object
>         obj is map<string, json>
>
> }
>
> rs = parseJson '{"name":"yeti", "version":1}'
> // this throws JsonError if the input wasn't an expected one
> println "version: \(rs.obj.['version'].num)";
> ----

I like this a lot and think will implement it for realy otherwise
unparsable JSON (ie names do not fit)

>
> Because well, the parseJson don't know the format of the expected Json.
> With the structOf or some another language extension it could be possible
> to describe the expected format to parse function, solving the issue.

As said IMO the structOf is not realy needed because sometimes you
just don't want to define the whole struct it is not worth it (ie if
you just get the latitude of google maps call) and in other cases the
json has to be andyway transfered to another struct.

And finally thats again one thing which I always like form yeti: it
is convient like a scripting language and as typesafe as you can get
it currently on the JVM and on top of this very simple.

Jean-Francois Poilpret

unread,
Feb 2, 2012, 2:43:15 AM2/2/12
to yeti...@googlegroups.com
Maybe one possibility, from the API viewpoint (I haven't checked how it could be implemented), would be to pass a template structure as an argument to parseJson:
 
template = {name = "", version = 0}
result = parseJson input template
 
the API of parseJson becomes:
    parseJson input template is string -> 'a -> 'a
if the input string cannot be parsed to an 'a, then an exception would be thrown
 
thus you get compile-time typesafety at the cost of adding one line to create the template structure
even if the actual JSON input string contains more than you expect in the template this is still OK, but you'll be able to use only what's defined by the template.
 
You might even generalize the API to provide a list of acceptable templates (as a variant with different structures) and return this variant, so that you can manage a JSON input which kind you don't know in advance (as long as you know a set of possible content types).
 
I had already though of a similar system for SQL queries, API-wise, I think it is OK, implementation might be an issue though (I didn't check this point).
Maybe this would be good to investigate further in that direction.
 
Cheers
 
Jean-Francois

chrisichris

unread,
Feb 2, 2012, 3:26:06 AM2/2/12
to yeti-lang
The Idea with the template seems to be a very good middleground with
the current compiler means.

Because madis said 'structOf' as compiler extension I think I
missunderstood that. Does 'structOf' mean that the compiler infers the
expected type and than dynamically checks it? ie

parseJson jsonString is string -> 'a=
(obj is ~Object = //generate yeti.lang.GenericStruct from json
result = strucOf obj
result);

Here the compiler not just casts the obj but also does a dynamic check
that the obj fits the infered type from the usage of result. If this
would be possible well that would be great.

Anyway in any other statically checked language for JVM you could not
even discuss this topic, because you would always have to explicitly
create a class or so and that is realy much more work compared to
dynamically typed languages.

Madis

unread,
Feb 2, 2012, 7:07:33 AM2/2/12
to yeti-lang

On Thu, 2 Feb 2012, chrisichris wrote:

> The Idea with the template seems to be a very good middleground with
> the current compiler means.
>
> Because madis said 'structOf' as compiler extension I think I
> missunderstood that. Does 'structOf' mean that the compiler infers the
> expected type and than dynamically checks it? ie
>
> parseJson jsonString is string -> 'a=
> (obj is ~Object = //generate yeti.lang.GenericStruct from json
> result = strucOf obj
> result);
>
> Here the compiler not just casts the obj but also does a dynamic check
> that the obj fits the infered type from the usage of result. If this
> would be possible well that would be great.

The structOf magic would work differently, it really is about being
a template, and being such, it doesn't need any later casting.

// --- description of the parser API ---

typedef object_parser = {
str is string -> string,
num is string -> number,
obj is (object_parser -> 'a) -> string -> 'a,
objs is (object_parser -> 'a) -> string -> list<'a>,
list is (source -> 'a) -> string -> list<'a>
}

typedef source_parsers = {
str is source -> string,
number is source -> number,
list is (source -> 'a) -> source -> list<'a>,
object is source -> (object_parser -> 'a) -> 'a,
}
parsers is source_parsers = ...;

jsonParser is string -> (object_parser -> 'a) -> 'a = ...;

// --- example usage ---

// myJSON1 is { str is string -> 'a, version is string -> 'b }
// -> { name is 'a, version is 'b }

myJSON1 = structOf { name = (.str), version = (.num) };
myJSON2 = structOf { foo {objs} = objs myJSON1 };

json = jsonParser myJSON2 '{foo: [{name: "", version: 0}]}';
for json.foo do i:
println (i.version + 1)
done;

... and thinking about it, the template-using JSON parser could reside
as thin wrapper on top of more low-level JSON parser implementation if
the structOf should get implemented (the source type above would then be
a parsed JSON tree in some format). It point would then be just providing
nicer/safer API, that verifies the parsed JSON and gives it back as a
structure.

I originally thought of structOf as a bit hackish way to implement
typesafe ORM, but here it looks like it might be more generally useful
(which is a stronger argument for actually implementing it).

chrisichris

unread,
Feb 2, 2012, 11:34:59 AM2/2/12
to yeti-lang
So structOf does transfer the structure of functions ie:

myJSON1 = structOf {name = (.str), version = (.num)}

becomes:

myJSON1 obj = {name = obj.str "name", version = obj.num "version"}

So it saves you rewrtiting the "fieldname" string arguments.

Can this not be also done with value-templates like Jean has proposed?

parseJson template jsonString =
(p = peekObject (template is 'a);
obj is ~Object = //use the peeked template to generate (validate)
the GenericStruct
obj unsafley_as 'a);

or if we realy get a compiler extension, maybe it would be possible to
pass in the type information as an argument which is provided
implicitly by the compiler.

parseJson jsonString [typeArg 'a] is string -> 'a = ....
(typeArg is Simple string | Function list<type> ... (type from
module yeti.lang.compiler.showtype)
obj is ~Object = //use typeArg to generate the GenericStruct
obj unsafely_as 'a);


//use it like
js = (parseJson '{name:"yeti", version:1}') is {name is string,
version is number}


Such a compiler generate refelctive type argument to provide a type at
runtime would be of course very useful in many situations. AFAIK scala
has that.





> ... and thinking about it, the template-using JSON parser could reside
> as thin wrapper on top of more low-level JSON parser implementation if
> the structOf should get implemented (the source type above would then be
> a parsed JSON tree in some format). It point would then be just providing
> nicer/safer API, that verifies the parsed JSON and gives it back as a
> structure.
>
> I originally thought of structOf as a bit hackish way to implement
> typesafe ORM, but here it looks like it might be more generally useful
> (which is a stronger argument for actually implementing it).
>
>
>
>
>
>
>
> > Anyway in any other statically checked language for JVM you could not
> > even discuss this topic, because you would always have to explicitly
> > create a class or so and that is realy much more work compared to
> > dynamically typed languages.
>
> > On 2 Feb., 08:43, Jean-Francois Poilpret <jfpoilp...@gmail.com> wrote:
> >> Maybe one possibility, from the API viewpoint (I haven't checked how it
> >> could be implemented), would be to pass a template structure as an argument
> >> to parseJson:
>
> >> template = {name = "", version = 0}
> >> result = parseJson input template
>
> >> the API of parseJson becomes:
> >> � � parseJson input template is string -> 'a -> 'a
> >>> �name = o.name ^ "", //testing for string
> >>> �turnover = o.turnover + 0,
> >>> �remark = maybeDefined \none (Some . (^ "")) o.remark,
> >>> }
>
> >>> this is much shorter than any other aproach I have tried so far
>
> >>>> The GenericStruct isn't really map<string, ~Object>. Sure, it uses String
> >>>> instances as keys, but like all Struct implementations, it does String
> >>>> object identity checking (java ==), and not value comparision.
>
> >>> I have extends yeti.lang.Hash now to implement the Struct interface.
> >>> Does this work? Because Hash uses equals and I guess there should be
> >>> no problem regarding identity two ident strings are of course equals
> >>> as well.
>
> >>>> class JsonError(String error) extends Exception(error) end
>
> >>>> typedef json = {
> >>>> � � � � /// throws JsonError if not number
> >>>> � � � � num is number,
> >>>> � � � � str is string,
> >>>> � � � � �list is list<json>,
> >>>> � � � � �/// throws JsonError if not JSON object
> >>>> � � � � obj is map<string, json>
>
> >>>> }
>
> >>>> rs = parseJson '{"name":"yeti", "version":1}'
> >>>> // this throws JsonError if the input wasn't an expected one
> >>>> println "version: \(rs.obj.['version'].num)";
> >>>> ----
>
> >>> I like this a lot and think will implement it for realy otherwise
> >>> unparsable JSON (ie names do not fit)
>
> >>>> Because well, the parseJson don't know the format of the expected Json.
> >>>> With the structOf or some another language extension it could be possible
> >>>> to describe the expected format to parse function, solving the issue.
>
> >>> As said IMO the structOf is not realy needed because sometimes you
> >>> just don't want to define the whole struct it is not worth it (ie if
> >>> you just get the latitude of google maps call) and in other cases the
> >>> json has to be andyway transfered to another struct.
>
> >>> And finally thats again one thing which I always like form yeti: it
> >>> is �convient like a scripting language and as typesafe as you can get

Madis

unread,
Feb 2, 2012, 3:57:11 PM2/2/12
to yeti-lang

On Thu, 2 Feb 2012, chrisichris wrote:

>> // myJSON1 is { str is string -> 'a, version is string -> 'b }
>> // � � � � � �-> { name is 'a, version is 'b }
>>
>> myJSON1 = structOf { name = (.str), version = (.num) };
>> myJSON2 = structOf { foo {objs} = objs myJSON1 };
>>
>> json = jsonParser myJSON2 '{foo: [{name: "", version: 0}]}';
>> for json.foo do i:
>> � � �println (i.version + 1)
>> done;
>
> So structOf does transfer the structure of functions ie:
>
> myJSON1 = structOf {name = (.str), version = (.num)}
>
> becomes:
>
> myJSON1 obj = {name = obj.str "name", version = obj.num "version"}
>
> So it saves you rewrtiting the "fieldname" string arguments.

In this case, yes.

> Can this not be also done with value-templates like Jean has proposed?
>
> parseJson template jsonString =
> (p = peekObject (template is 'a);
> obj is ~Object = //use the peeked template to generate (validate)
> the GenericStruct
> obj unsafley_as 'a);

In that example code, 'a type variables in separate type declarations are
not associated, making (template is 'a) quite pointless. Given that you
add type declaration and write:

parseJson template jsonString is 'a -> string -> 'a = ...;

a non-sense like parseJson id 'wtf' 1 would still compile.

> or if we realy get a compiler extension, maybe it would be possible to
> pass in the type information as an argument which is provided
> implicitly by the compiler.
>
> parseJson jsonString [typeArg 'a] is string -> 'a = ....
> (typeArg is Simple string | Function list<type> ... (type from
> module yeti.lang.compiler.showtype)
> obj is ~Object = //use typeArg to generate the GenericStruct
> obj unsafely_as 'a);
>
> //use it like
> js = (parseJson '{name:"yeti", version:1}') is {name is string,
> version is number}
>
> Such a compiler generate refelctive type argument to provide a type at
> runtime would be of course very useful in many situations. AFAIK scala
> has that.

This is far more magic to incorporate into function call runtime than I
would want. Essentially it's passing the return type as literal value in
hidden argument. How it would work together with curring and type
inference (where the return type is often not known at call site, and not
all arguments have to be passed at once) among other things, I'm not sure.
It is possible (well, Haskell type classes also do something remotly
similar behind the scenes), but most probably is quite complicated,
especially with current compilation model.

chrisichris

unread,
Feb 2, 2012, 5:20:47 PM2/2/12
to yeti-lang
Thanks for the explanation, I will add your original proposed typedef:

typedef json = {
/// throws JsonError if not number
num is number,
str is string,
list is list<json>,
/// throws JsonError if not JSON object
obj is map<string, json>
}

and also the unsafe version.

One unrelated question: would it be possible to implicitly define
typedefs, meaning that I can create typedefs from a function
declaration. ie

parseJson jsonString is string -> ['a typedef json] = {
get num () = ...,
get str () = failWith ("no string")
...
};

handleJson js is json -> 'a = ....

so that the compiler defines the typedef json from the inferred type.

Thats because I often have this pattern that I have a struct-factory
function and than want to use exactly that struct in other functions
and than I have to do a lot of typedef, while it is already known to
the compiler.




On 2 Feb., 21:57, Madis <ma...@cyber.ee> wrote:
> On Thu, 2 Feb 2012, chrisichris wrote:
> >> // myJSON1 is { str is string -> 'a, version is string -> 'b }
> >> // � � � � � �-> { name is 'a, version is 'b }
>
> >> myJSON1 = structOf { name = (.str), version = (.num) };
> >> myJSON2 = structOf { foo {objs} = objs myJSON1 };
>
> >> json = jsonParser myJSON2 '{foo: [{name: "", version: 0}]}';
> >> for json.foo do i:
> >> � � �println (i.version + 1)

Madis

unread,
Feb 4, 2012, 9:10:48 AM2/4/12
to yeti-lang

On Thu, 2 Feb 2012, chrisichris wrote:

> One unrelated question: would it be possible to implicitly define
> typedefs, meaning that I can create typedefs from a function
> declaration. ie
>
> parseJson jsonString is string -> ['a typedef json] = {
> get num () = ...,
> get str () = failWith ("no string")
> ...
> };
>
> handleJson js is json -> 'a = ....
>
> so that the compiler defines the typedef json from the inferred type.
>
> Thats because I often have this pattern that I have a struct-factory
> function and than want to use exactly that struct in other functions
> and than I have to do a lot of typedef, while it is already known to
> the compiler.

Easiest way would be introducing non-polymorphic typedefs, like...

typedef shared json = 'a

parseJson jsonString is string -> json = {
...
}

fobar x is json -> string =
x.foo;

The shared keyword would imply unification with the same type expression
in typedef, without making a copy of it before, therefore connecting the
types.

Madis

unread,
Feb 4, 2012, 5:16:42 PM2/4/12
to yeti-lang

I implemented these shared typedefs now (it really was quite easy - the
shared typedef just omits coping free type variables in type into new
type variables in the use site, and shares them between all use sites).

With the shared typedef declaration the obj typedef gets it's type from
the fun using it (and multiple usages in that module would all affect that
shared obj type). In the code using that module it acts as normal typedef.

predator.9062:~/yeti$ cat shared1.yeti
module shared1;

typedef shared obj = 'a

{ fun x is 'a -> obj = {x} }

predator.9062:~/yeti$ cat shared2.yeti
load shared1;

test x is obj -> () =
println x.x;

test (fun '22');
test (fun 23);
predator.9062:~/yeti$ yeti shared2.yeti
22
23

chrisichris

unread,
Feb 4, 2012, 7:23:50 PM2/4/12
to yeti-lang
Wow that's great. Thanks a lot.

Jean-Francois Poilpret

unread,
Feb 12, 2012, 4:57:01 PM2/12/12
to yeti...@googlegroups.com
Following my initial suggestion of using a template argument which would
define the expected type, I have digged further into this direction and
came to the following conclusion:

- doing it 100% in yeti is not feasible because yeti's type system tries
to be too "smart" at guessing function types; I abandoned this way after
several days trying to "trick" the type-system
- but if you use java for implementing the raw code, then you can get it
to work

I have finally implemented, quite simply, the java + yeti way; you can
take a look at it on https://github.com/jfpoilpret/yeti-json

In the end, from yeti's side, it all boils down to 2 functions:

readJson template input is 'a -> string -> 'a = ...
readJson' templates input is list<'a> -> string -> 'a = ...

The first one is used when the input string is expected to be parsable
to exactly one type (typically a struct, but lists are also supported).
The second one is used when the input string may be parsed to one of
several types, in this case, you pass a list of yeti variants to sepcify
all possible template types and you can then use pattern matching to
discover which type the input could be parsed to:

input = ...;
case readJson' [Account {name = "", balance = 0}, Error {code = 0,
reason = ""}] input of
Account {name, balance}: ...;
Error {code, reason}: ...;
esac;

I find this way quite clean, easy to use, and not very verbose overall.

Don't hesitate to take a look and comment

Cheers

Jean-Francois

Madis

unread,
Feb 12, 2012, 5:47:05 PM2/12/12
to yeti...@googlegroups.com

On Sun, 12 Feb 2012, Jean-Francois Poilpret wrote:

> Following my initial suggestion of using a template argument which would
> define the expected type, I have digged further into this direction and came
> to the following conclusion:
>
> - doing it 100% in yeti is not feasible because yeti's type system tries to
> be too "smart" at guessing function types; I abandoned this way after several
> days trying to "trick" the type-system

If the problem was compiler inferring the argument type, using
"foo = x unsafely_as ~Object;" should disconnect the x type from foo.

> - but if you use java for implementing the raw code, then you can get it to work
>
> I have finally implemented, quite simply, the java + yeti way; you can take a
> look at it on https://github.com/jfpoilpret/yeti-json
>
> In the end, from yeti's side, it all boils down to 2 functions:
>
> readJson template input is 'a -> string -> 'a = ...
> readJson' templates input is list<'a> -> string -> 'a = ...
>
> The first one is used when the input string is expected to be parsable to
> exactly one type (typically a struct, but lists are also supported).
> The second one is used when the input string may be parsed to one of several
> types, in this case, you pass a list of yeti variants to sepcify all possible
> template types and you can then use pattern matching to discover which type
> the input could be parsed to:
>
> input = ...;
> case readJson' [Account {name = "", balance = 0}, Error {code = 0, reason =
> ""}] input of
> Account {name, balance}: ...;
> Error {code, reason}: ...;
> esac;
>
> I find this way quite clean, easy to use, and not very verbose overall.

Looks quite good.

Jean-Francois Poilpret

unread,
Feb 12, 2012, 6:30:58 PM2/12/12
to yeti...@googlegroups.com
On 12-02-2012 23:47, Madis wrote:
>
> On Sun, 12 Feb 2012, Jean-Francois Poilpret wrote:
>
>> Following my initial suggestion of using a template argument which
>> would define the expected type, I have digged further into this
>> direction and came to the following conclusion:
>>
>> - doing it 100% in yeti is not feasible because yeti's type system
>> tries to be too "smart" at guessing function types; I abandoned this
>> way after several days trying to "trick" the type-system
>
> If the problem was compiler inferring the argument type, using
> "foo = x unsafely_as ~Object;" should disconnect the x type from foo.
Originally I did that, and little by little, my code was covered with a
lot of these casts, which made it very difficult to read and improve.
But the final problem I could not deal with was when I started handling
lists (after handling only structs).
The code was very ugly in the end and I'd rather not post it here; maybe
I'll put it to github just to show what point I hard reached.
In the end I believed this couldn't be solved in pure yeti or, if it
could, it would require so much more ugly code that it would be pointless.
Hence my choice to switch to Java code and minimize the yeti code to the
API that interfaces to Java and makes the necessary casts to make the
system typesafe.

chrisichris

unread,
Feb 14, 2012, 6:32:28 AM2/14/12
to yeti-lang
That looks realy good

Thanks for this api.

Just one "feature-request": handling of json-null values. Maybe
templates good take (None () | Some v) values and depending wheter the
field is not prestent/null the value gets none else Some v

Thanks,
Christian

On 12 Feb., 22:57, Jean-Francois Poilpret <jfpoilp...@gmail.com>
wrote:
> Following my initial suggestion of using a template argument which would
> define the expected type, I have digged further into this direction and
> came to the following conclusion:
>
> - doing it 100% in yeti is not feasible because yeti's type system tries
> to be too "smart" at guessing function types; I abandoned this way after
> several days trying to "trick" the type-system
> - but if you use java for implementing the raw code, then you can get it
> to work
>
> I have finally implemented, quite simply, the java + yeti way; you can
> take a look at it onhttps://github.com/jfpoilpret/yeti-json

Jean-Francois Poilpret

unread,
Feb 14, 2012, 7:31:52 AM2/14/12
to yeti...@googlegroups.com
On 14 February 2012 12:32, chrisichris <christ...@googlemail.com> wrote:
That looks realy good
 
Thanks for the compliment.
 
 

Thanks for this api.

Just one "feature-request": handling of json-null values. Maybe
templates good take (None () | Some v) values and depending wheter the
field is not prestent/null the value gets none else Some v
 
I thought about this initially but I thought this is not feasible directly with just one template. How could one template specify that a field is either None () or Some x, the template is a struct (not a type) hence it can be either Some x or None () but not both at the same time.
I believe that if I specify {option = Some 0} then its type cannot accept None (), right?
 
Or am I mistaken by yeti's typesystem?
 
I'll try it at home this evening and update github if it works
 
Jean-François

chrisichris

unread,
Feb 14, 2012, 7:53:09 AM2/14/12
to yeti-lang
Afaik You could write

template = {foo = (Some 1) is (None () | Some number)}

a littel helper

some x = (Some (x is 'a)) is None () | Some 'a;

template = {foo = some 1}

BTW would it make sense to have the maybe type in std, because thats
what is always needed.

typedef maybe<a> = None () | Some a

christian

On 14 Feb., 13:31, Jean-Francois Poilpret <jfpoilp...@gmail.com>
wrote:

Madis

unread,
Feb 14, 2012, 8:00:33 AM2/14/12
to yeti-lang

On Tue, 14 Feb 2012, chrisichris wrote:

> Afaik You could write
>
> template = {foo = (Some 1) is (None () | Some number)}
>
> a littel helper
>
> some x = (Some (x is 'a)) is None () | Some 'a;
>
> template = {foo = some 1}

Won't help, that type information isn't avaible at runtime.

Jean-Francois Poilpret

unread,
Feb 14, 2012, 8:56:09 AM2/14/12
to yeti...@googlegroups.com
If it's not available at runtime, that shouldn't be a real problem (my implementation can look for Some x only and convert JSON value either to Some z or None ()).
My concern was type-checking at compile-time, let's say I have:
 
x = readJson {option = Some 0} '{"option": null}';
Yeti compiler would see x as type {option is Some number}.
Then can I pattern match x on Some z / None ()?
If the compiler accepts it in all situations then it should be fine.
 
According to some experiments I had with the REPL, it should be fine, but if the compiler tried to optimize pattern-matching (eg no need to check the case None () because type is supposed to be Some number), then this wouldn't work any longer.
 

Madis

unread,
Feb 14, 2012, 9:09:16 AM2/14/12
to yeti...@googlegroups.com

On Tue, 14 Feb 2012, Jean-Francois Poilpret wrote:

> On 14 February 2012 14:00, Madis <ma...@cyber.ee> wrote:
>
> On Tue, 14 Feb 2012, chrisichris wrote:
>
> Afaik You could write
>
> template = {foo = (Some 1) is (None () | Some number)}
>
> a littel helper
>
> some x = (Some (x is 'a)) is None () | Some 'a;
>
> template = {foo = some 1}
>
> Won't help, that type information isn't avaible at runtime.
>
> If it's not available at runtime, that shouldn't be a real problem (my implementation can look for
> Some x only and convert JSON value either to Some z or None ()).
> My concern was type-checking at compile-time, let's say I have:
> �
> x = readJson {option = Some 0} '{"option": null}';
> Yeti compiler would see x as type {option is Some number}.
> Then can I pattern match x�on Some�z / None ()?
> If the compiler accepts it in all situations then it should be fine.

Yes, you can have pattern match cases that are not in the matched, for
example:

> case A () of A _: 1; B _: 2 esac
1 is number

What you're essentially proposing, is lying about the actual type. In the
worst case for variant pattern-match it should result in bad match runtime
error:

> case None () unsafely_as ~yeti.lang.Tag unsafely_as A () of A _: 1 esac
java.lang.IllegalArgumentException: bad match (None [])
at yeti.lang.Core.badMatch(Core.java:199)
at code.apply(<>)

Not the end of world, but you are losing some type safety - the compiler
can't enforce pattern checks about variant it doesn't know to exists.

Jean-Francois Poilpret

unread,
Feb 14, 2012, 3:54:59 PM2/14/12
to yeti...@googlegroups.com
On 14-02-2012 15:09, Madis wrote:
> What you're essentially proposing, is lying about the actual type. In
> the worst case for variant pattern-match it should result in bad match
> runtime error:
>> case None () unsafely_as ~yeti.lang.Tag unsafely_as A () of A _: 1 esac
> java.lang.IllegalArgumentException: bad match (None [])
> at yeti.lang.Core.badMatch(Core.java:199)
> at code.apply(<>)
>
> Not the end of world, but you are losing some type safety - the
> compiler can't enforce pattern checks about variant it doesn't know to
> exists.

Well that would not be exactly so, because anyone using Some x construct
knows you need to use it with pattern-matching (or the maybe function
that uses pattern-matching).
For info, I have just committed changes to github, that includes
handling of JSON null.

It is now possible to write:
> y = readJson {option = Some 0} '{"option": null}';
this works, but as said before, YETI sees "y" with a type that doesn't
match reality:
y is {option is Some number} = {option=None []}
you can still use y.option in a pattern matching construct:
> case y.option of Some z: z; None (): -1 esac
that will print -1 because y.option is None ()

Also, I have added another possible mapping for JSON null, it can be
mapped to a yeti list; but this mapping is not as interesting because
non null value in JSON input must then be a list.


Madis

unread,
Feb 14, 2012, 4:31:52 PM2/14/12
to yeti...@googlegroups.com

On Tue, 14 Feb 2012, Jean-Francois Poilpret wrote:

> On 14-02-2012 15:09, Madis wrote:
>> What you're essentially proposing, is lying about the actual type. In the
>> worst case for variant pattern-match it should result in bad match runtime
>> error:
>>> case None () unsafely_as ~yeti.lang.Tag unsafely_as A () of A _: 1 esac
>> java.lang.IllegalArgumentException: bad match (None [])
>> at yeti.lang.Core.badMatch(Core.java:199)
>> at code.apply(<>)
>>
>> Not the end of world, but you are losing some type safety - the compiler
>> can't enforce pattern checks about variant it doesn't know to exists.
>
> Well that would not be exactly so, because anyone using Some x construct
> knows you need to use it with pattern-matching (or the maybe function that
> uses pattern-matching).
> For info, I have just committed changes to github, that includes handling of
> JSON null.
>
> It is now possible to write:
>> y = readJson {option = Some 0} '{"option": null}';
> this works, but as said before, YETI sees "y" with a type that doesn't match
> reality:
> y is {option is Some number} = {option=None []}
> you can still use y.option in a pattern matching construct:
>> case y.option of Some z: z; None (): -1 esac
> that will print -1 because y.option is None ()

You can write case y.option of Some z: z esac, it should compile as the
type of option is just Some number, and it will fail at runtime with
"bad match" error. Also you could have None x: x + 1 variant in case,
and it would fail with NullPointerException (as () is represented by
Java null). That is what I meant with losing some type safety.

> Also, I have added another possible mapping for JSON null, it can be mapped
> to a yeti list; but this mapping is not as interesting because non null value
> in JSON input must then be a list.

From type-system point-of-view it would be more correct. ;)

chrisichris

unread,
Feb 14, 2012, 4:54:27 PM2/14/12
to yeti-lang
Why don't just use the little some helper:

> some is 'a -> None () | Some 'a = Some;

> y = readJson {option = some 0} '{"option": null}';

should give than

y is {option is Some number | None ()} = {option=None []}

and the compiler should complain if used with the wrong match.



On 14 Feb., 21:54, Jean-Francois Poilpret <jfpoilp...@gmail.com>
wrote:

Jean-Francois Poilpret

unread,
Feb 15, 2012, 1:03:09 AM2/15/12
to yeti...@googlegroups.com
On 14-02-2012 22:31, Madis wrote:
> You can write case y.option of Some z: z esac, it should compile as
> the type of option is just Some number, and it will fail at runtime
> with "bad match" error. Also you could have None x: x + 1 variant in
> case, and it would fail with NullPointerException (as () is
> represented by Java null). That is what I meant with losing some type
> safety.
One possibility to solve this problem is to add one level of indirection
in the template (and hence the result as well):

> some x _ = if true then Some x else None () fi;
> x = readJson {option = some 0} '{"option": null}'
> x.option ()
type here is Some number | None ()

In java implementation, the trick would consist in replacing the "some"
function in option field by a constant function, that returns Some x or
None () based on JSON input (x or null).
That seems feasible, I will try it tonight.

>> Also, I have added another possible mapping for JSON null, it can be
>> mapped to a yeti list; but this mapping is not as interesting because
>> non null value in JSON input must then be a list.
> From type-system point-of-view it would be more correct. ;)

Yes but less useful because I don't think people use JSON null only for
lists, hence the mapping to yeti would not fit reality in most cases.

jfpoilpret

unread,
Feb 16, 2012, 1:21:55 AM2/16/12
to yeti-lang
Just committed the new implementation to github.
Now you can do:

load json;

y = readJson {option = some 0} '{"option": null}';
println y.option ();
z = readJson {option = some 0} '{"option": 123}';
println z.option ();

The type of y.option () is seen by Yeti as Some number | None (),
which is now fully safe.

On Feb 15, 7:03 am, Jean-Francois Poilpret <jfpoilp...@gmail.com>
wrote:

chrisichris

unread,
Mar 15, 2013, 1:59:29 PM3/15/13
to yeti...@googlegroups.com, ma...@cyber.ee
Or this using accessor fields:

----
class JsonError(String error) extends Exception(error) end

typedef json = {
         /// throws JsonError if not number
         num is number,
         str is string,
         list is list<json>,
         /// throws JsonError if not JSON object
         obj is map<string, json>
}

rs = parseJson '{"name":"yeti", "version":1}'
// this throws JsonError if the input wasn't an expected one
println "version: \(rs.obj.['version'].num)";


According to your above advice I have implemented a Json parser (which works fine so far). This works like this:

typdef json = {
   str is string,
   num is number,
   bool is boolean,
   obj is hash<string,json>,
   arr is list<json>
}
  
base = {
  get str () = failWith "no String",
  get num () = failWith "no number",
  get bool() = failWith "no boolean",
  get obj () = failWith "no obj",
  get arr () = failWith "no arry
} is json;
  

parseJson x = 
(//do the parsing.....
  case parseToken of
  Number n:
     base with {num = n};
  Obj o:
     ha = [:];
     setHashDefault ha do k v: 
        parseJson o#get(k)
     done;
     base with {obj = ha};
....

Now the problem is when I create such a struct in the repl the repl triess to print it and that way invokes all the properties which results of course in an exception (because they all fail - except one)

Do you know a way around that maybe the string function could check for a "toString" property and if it is present use it to render the struct as string? 
   
 

chrisichris

unread,
Mar 15, 2013, 2:37:09 PM3/15/13
to yeti...@googlegroups.com, ma...@cyber.ee
Sorry for reposting: 

How about some function setToString strFn value is ('a -> string) -> 'a -> (), which would set on the underlying Struct (or ByKey, Fun) Object a toString function which is used in the java toString() method (Strings, number booleans would be unaffected).

chrisichris

unread,
Mar 16, 2013, 2:30:48 AM3/16/13
to yeti...@googlegroups.com, ma...@cyber.ee
Or maybe what is propably the right thing: treat properties in a struct like functions because == also fails

chrisichris

unread,
Mar 16, 2013, 3:29:41 AM3/16/13
to yeti...@googlegroups.com, ma...@cyber.ee
So my realy last comment on this:

Finally I think yeti currently does the right thing. It should fail, properties should be values (which are transparent created by a funct) and if a struct is printed (or compared) and there is no value it should fail.

I think I have to find another way for the json implementations (maybe returning only options and the user has

maybe' noneFn someFn option = ..;

fail () = failWith "none";

typedef json = {
   str = None () | Some str,
   num = None () | Some num,
}

//use it

maybe' fail (js.str);


Sorry for the confusion

Madis

unread,
Mar 16, 2013, 7:07:11 AM3/16/13
to yeti...@googlegroups.com

On Fri, 15 Mar 2013, chrisichris wrote:

> Now the problem is when I create such a struct in the repl the repl triess to print it and that
> way invokes all the properties which results of course in an exception (because they all fail -
> except one)
>
> Do you know a way around that maybe the string function could check for a "toString" property and
> if it is present use it to render the struct as string?�

Yes, having a way to override struct toString might be useful, I'll add it
to TODO list to think about it.

chrisichris

unread,
Mar 17, 2013, 3:26:20 AM3/17/13
to yeti...@googlegroups.com, ma...@cyber.ee
Thanks


On Saturday, March 16, 2013 12:07:11 PM UTC+1, Madis wrote:

On Fri, 15 Mar 2013, chrisichris wrote:

> Now the problem is when I create such a struct in the repl the repl triess to print it and that
> way invokes all the properties which results of course in an exception (because they all fail -
> except one)
>
> Do you know a way around that maybe the string function could check for a "toString" property and
> if it is present use it to render the struct as string?�

Madis

unread,
Sep 22, 2013, 5:49:31 PM9/22/13
to yeti...@googlegroups.com

I've been thinking about the untyped JSON support.

There are multiple aspects, but for one, the current experimental JSON
module has field reference syntax, that is still a bit too cumbersome.
Chrisichris has had multiple different proposals, and from the opaque
types one I like the idea of special operator. However, I think that such
"pointer" operator should be generic and in standard library.
In Haskell one would use type class, in Yeti the nearest thing to type
class is structure:

> (->) object member = object.``->`` member
-> is {.-> is 'a -> 'b} -> 'a -> 'b = <code$$m$g>

This would work with any structure defining the -> field in itself.

Since Yeti currently doesn't have user-definable operator priorities,
-> should be added to the compilers priority table with same priority as
the .member reference operator.

There have been worries about efficiency of using structures for all JSON
values, but I think this isn't really a big problem. The compiler
generates quite efficient structure implementations, and if needed,
further optimization could be done by implementing custom structure
classes (as the atomic function does). The wrapping structures could be
allocated on-the-fly, and temporary object allocation is cheap in JVM.

In the end it could work like the following:

obj = jsonParse someString;
totalBar = obj->'foo'->'bar'.list |> map (.num) |> sum;

Christian Essl

unread,
Sep 23, 2013, 3:45:03 AM9/23/13
to yeti...@googlegroups.com
Hi,

That sounds very nice. 

Just some stupid questions out of my head Please see them realy just as brain-storming:

1.) Maybe the (->) operator could be realy defined as 'accessing named members' and therefore use string as member-name:  (->) is {.-> is string -> 'a} -> string -> 'a. And than - when you have to change the compiler anyway - any symbol after (->) is treated as string-literal also without quotes. So that instead of obj -> 'foo' -> 'bar' one could just wirte obj -> foo -> bar.list |> map .... I think than the usage-scope of the operator is better defined which makes code easier to read and leaving of the quotes makes it easier to write. In case you need an expression you can always use the function on the struct directly without the (->) operator.
 
2.) Maybe the function on the structure could be called 'getMember' or so for that purpose instead of beeing itself an operator-notation

3.) Finally maybe the operator could also give in the original structure
(->) obj member = object.´´->´´ obj member;

Same as you once mentioned with the lua (:) operator. Than one funciton could be used with different structs - no closure needed - and it would be easier to override the behaviour.

4.) Is there anywhere defined how the general operator precedence is? Especially for custom operators or those in yeti.lang.std (or, and, not etc) is thhere any special precedence between them?

As said. This is just out of my head. And your (->) is realy nice also without these questionable additions. I think it is also the best pattern for ad-hoc polymorphism I have seen so far.

Thanks,
Christian 
  


>  





--
You received this message because you are subscribed to the Google Groups "yeti-lang" group.
To unsubscribe from this group and stop receiving emails from it, send an email to yeti-lang+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

chrisichris

unread,
Oct 1, 2013, 2:07:22 PM10/1/13
to yeti...@googlegroups.com, ma...@cyber.ee
Another take where the Json values are wrapped in Tags:

typedef json = 
   String string 
   | Number number
   | Boolean boolean
   | List list<json>
   | Hash hash<string,json>  //for object
   | None (); //for null 

//extractors
isStr = \case of String s: s is string; x : failWith "No string but \(x)" esac;
isNum = \case of Number n: n is number; x : failWith "No number but \(x)" esac;
isBool = \case of Boolean b: b is boolean; x : failWith "No  boolena but \(x)" esac;

//for handling lists
//ie (json |> isListOf isStr) is list<string> 
isListOf mfn = \case of
    List ls: map mfn ls;
    None (): [];
    _ : failWith "No list value \(x)";
esac;

//for handling null value
// ie: (json |> isMaybe isStr) is None () | Some string; 
isMaybe cfn = \case of
    None (): none;
    x : Some (cfn x);
esac; 

//the point operator
//either uses the Hash tag to look up the field in an hash or the Struct tag to get a value function to lookup the tag. 
//this is to be compatible with std/peekObject and give implementors more flexibility to use either hashes (plain values) or functions   
(->) obj name = case obj of
    Struct {value} : value (name is string); //same adhoc polymorphism possible as with Madis's (->) proposal but compatible with peekObject
    Hash ha: ha[name];
    _ : none
esac;

//usage
>js is json = parseJson '{"foo": {"bar" : [1,2,3]}, "bar" : null}';
>js -> 'foo' -> 'bar' |> isListOf isNum
[1,2,3]

>js -> 'bar' |> isMaybe isStr
None () is None () | Some string

//and works also for peekObject
> p = peekObject {foo = {bar = [1,2,3]}};
> p -> 'foo' -> 'bar' |> isListOf isNum
[1,2,3]

IMO the advantages over the json as struct is that are values and that the runtime memory overhead is lower. Additional it does not need any compiler changes.

What do you thing of that.

Thanks

Madis Janson

unread,
Oct 2, 2013, 9:32:34 AM10/2/13
to yeti...@googlegroups.com

On Mon, 23 Sep 2013, Christian Essl wrote:

> Hi,
> That sounds very nice.�
>
> Just some stupid questions out of my head Please see them realy just as
> brain-storming:
>
> 1.) Maybe the (->) operator could be realy defined as 'accessing named
> members' and therefore use string as member-name: �(->) is {.-> is string ->
> 'a} -> string -> 'a. And than - when you have to change the compiler anyway
> - any symbol after (->) is treated as string-literal also without quotes. So
> that instead of obj -> 'foo' -> 'bar' one could just wirte obj -> foo ->
> bar.list |> map .... I think than the usage-scope of the operator is better
> defined which makes code easier to read and leaving of the quotes makes it
> easier to write. In case you need an expression you can always use the
> function on the struct directly without the (->) operator.

It might useful, but on the other side it makes things more complicated -
it would be a special case, and if you want to give non-constant string
as member name, then it would force you to use the field directly
(foo.``->`` bar). I'm not sure whether it's really a good or bad idea.

> 2.) Maybe the function on the structure could be called 'getMember' or so
> for that purpose instead of beeing itself an operator-notation

Maybe, but on the other hand, this might collide with some other uses.

> 3.) Finally maybe the operator could also give in the original structure
> (->) obj member = object.��->�� obj member;
>
> Same as you once mentioned with the lua (:) operator. Than one funciton
> could be used with different structs - no closure needed - and it would be
> easier to override the behaviour.

Is there any good use case, where this would help?
You have to consider, that -> return type cannot depend on the method name?

> 4.) Is there anywhere defined how the general operator precedence is?
> Especially for custom operators or those in yeti.lang.std (or, and, not etc)
> is thhere any special precedence between them?

Currently its defined in Yeti parser. Seems that it is missing in the
documenation.

Madis Janson

unread,
Oct 2, 2013, 9:54:01 AM10/2/13
to yeti...@googlegroups.com
It's unlikely to be any faster or more memory efficient than properly
optimised structure based implementation (and probably it has more
overhead).

> Additional it does not need any compiler changes.

Only compiler change that was needed structure based by (->) was
introducing new operator priority. It would work without it, but would be
more cumbersome to use. The same is probably true here (you've worked
around it using |>, which could be done as well for structure based ->).

(the problem with toString can be worked around, when writing optimised
implementation)

chrisichris

unread,
Oct 2, 2013, 11:17:41 AM10/2/13
to yeti...@googlegroups.com, ma...@cyber.ee


On Wednesday, October 2, 2013 3:54:01 PM UTC+2, Madis Janson wrote:

On Tue, 1 Oct 2013, chrisichris wrote:

> Another take where the Json values are wrapped in Tags:
> typedef json =�
> � �String string�
> � �| Number number
> � �| Boolean boolean
> � �| List list<json>
> � �| Hash hash<string,json> �//for object
> � �| None (); //for null�
>
> //extractors
> isStr = \case of String s: s is string; x : failWith "No string but \(x)"
> esac;
> isNum = \case of Number n: n is number; x : failWith "No number but \(x)"
> esac;
> isBool = \case of Boolean b: b is boolean; x : failWith "No �boolena but
> \(x)" esac;
>
> //for handling lists
> //ie (json |> isListOf isStr) is list<string>�
> isListOf mfn = \case of
> � � List ls: map mfn ls;
> � � None (): [];
> � � _ : failWith "No list value \(x)";
> esac;
>
> //for handling null value
> // ie: (json |> isMaybe isStr) is None () | Some string;�
> isMaybe cfn = \case of
> � � None (): none;
> � � x : Some (cfn x);
> esac;�
>
> //the point operator
> //either uses the Hash tag to look up the field in an hash or the Struct tag
> to get a value function to lookup the tag.�
> //this is to be compatible with std/peekObject and give implementors more
> flexibility to use either hashes (plain values) or functions ��
> (->) obj name = case obj of
> � � Struct {value} : value (name is string); //same adhoc polymorphism
> possible as with Madis's (->) proposal but compatible with peekObject
> � � Hash ha: ha[name];
> � � _ : none
> esac;
>
> //usage
> >js is json = parseJson '{"foo": {"bar" : [1,2,3]}, "bar" : null}';
> >js -> 'foo' -> 'bar' |> isListOf isNum
> [1,2,3]
>
> >js -> 'bar' |> isMaybe isStr
> None () is None () | Some string
>
> //and works also for peekObject
> > p = peekObject {foo = {bar = [1,2,3]}};
> > p -> 'foo' -> 'bar' |> isListOf isNum
> [1,2,3]
>
> IMO the advantages over the json as struct is that are values and that the
> runtime memory overhead is lower.

It's unlikely to be any faster or more memory efficient than properly
optimised structure based implementation (and probably it has more
overhead).

> Additional it does not need any compiler changes.

Only compiler change that was needed structure based by (->) was
introducing new operator priority. It would work without it, but would be
more cumbersome to use. The same is probably true here (you've worked
around it using |>, which could be done as well for structure based ->).

(the problem with toString can be worked around, when writing optimised
implementation)

It is not only the problem of toString also with equals/hashCode, deep copying and with serialization/deserialization on different JVM - for the general case. Sure for json there will be an optimised implementation which does all that right and ships with the standard yeti lib and so is availabel in all JVMs which run yeti, but for other cases (ie BSON, SQL, general DataMapping and working with untyped data) this will not be the case and these will than be much harder to implement. I think the Variant based aproach is in the general case easier and more straight forward, because it is just data with seperated functions and not objects. This makes also reasing about polymorphism easier - for people like me who not always understand the full restrictions ie value-restriction. Also the reuse-level is IMO much higher. If I implement ie a BSON-wrapper (the mongodb format) than I just implement a function which translates DBObject to the Variant type and an isObjectRef function and can otherwise use the normal json functions.   


Madis

unread,
Oct 3, 2013, 7:31:43 AM10/3/13
to yeti...@googlegroups.com
> functions. ��

So lets implement a simple wrapper in Yeti:

------- objwrap.yeti
module objwrap;

wrap obj is ~Object -> 'a = {
get (->) () key =
wrap (obj unsafely_as ~java.util.Map)#get(key),
get str () = obj unsafely_as ~String as string,
get num () = (obj unsafely_as ~java.lang.Number)#doubleValue(),
get bool () = (obj unsafely_as ~Boolean)#booleanValue(),
get null? () = nullptr? obj
};

{ wrap }

------- testwrap.yeti
load objwrap;
m = new java.util.HashMap();
m#put("kkk", 44);
m2 = new java.util.HashMap();
m2#put("savi", "heh");
m#put("ppp", m2);
w = wrap m;

(->) obj field = obj.``->`` field;
println (w->'kkk').num;
println (((w->'ppp')->'savi').str ^ 'test');
println (w->'zzz').null?;

-------
$ yeti testwrap.yeti
44.0
hehtest
true
$ yeti -C objwrap.yeti
$ ls *.class
objwrap.class objwrap$wrap$0.class objwrap$wrap$0$.class objwrap$wrap.class
$ javap -c 'objwrap$wrap'
Compiled from "objwrap.yeti"
public final class objwrap$wrap extends yeti.lang.Fun {
public static final yeti.lang.Fun _;

public final java.lang.Object apply(java.lang.Object);
Code:
0: new #16 // class objwrap$wrap$0
3: dup
4: invokespecial #17 // Method objwrap$wrap$0."<init>":()V
7: dup
8: aload_1
9: putfield #21 // Field objwrap$wrap$0._5:Ljava/lang/Object;
12: areturn

static {};
Code:
0: new #2 // class objwrap$wrap
3: dup
4: invokespecial #23 // Method "<init>":()V
7: putstatic #27 // Field _:Lyeti/lang/Fun;
10: return
}

Here the wrap function just intantiates compiler generated structure
implementation objwrap$wrap$0, and puts the wrapped object into its _5
field. What the structure is?

$ javap -private -c 'objwrap$wrap$0'
Compiled from "objwrap.yeti"
final class objwrap$wrap$0 extends yeti.lang.AStruct {
java.lang.Object _5;

Single field representing the wrapped object.

public objwrap$wrap$0();
Code:
0: aload_0
1: getstatic #15 // Field objwrap._0:[Ljava/lang/String;
4: getstatic #19 // Field objwrap._1:[Z
7: invokespecial #22 // Method yeti/lang/AStruct."<init>":([Ljava/lang/String;[Z)V
10: return

Dispather function checking the field.

public java.lang.Object get(java.lang.String);
Code:
0: aload_0
1: aload_1
2: ldc #26 // String ->
4: if_acmpne 12
7: iconst_0
8: invokevirtual #29 // Method get:(I)Ljava/lang/Object;
11: areturn
12: aload_1
13: ldc #31 // String str
15: if_acmpne 23
18: iconst_4
19: invokevirtual #29 // Method get:(I)Ljava/lang/Object;
22: areturn
23: aload_1
24: ldc #33 // String num
26: if_acmpne 34
29: iconst_3
30: invokevirtual #29 // Method get:(I)Ljava/lang/Object;
33: areturn
34: aload_1
35: ldc #35 // String bool
37: if_acmpne 45
40: iconst_1
41: invokevirtual #29 // Method get:(I)Ljava/lang/Object;
44: areturn
45: aload_1
46: ldc #37 // String null?
48: if_acmpne 56
51: iconst_2

Invokes the next method.
52: invokevirtual #29 // Method get:(I)Ljava/lang/Object;
55: areturn
56: new #39 // class java/lang/NoSuchFieldException
59: dup
60: aload_1
61: invokespecial #42 // Method java/lang/NoSuchFieldException."<init>":(Ljava/lang/String;)V
64: athrow

public java.lang.Object get(int);
Code:
0: aload_0
1: iload_1
2: tableswitch { // 0 to 4
0: 36
1: 50
2: 75
3: 95
4: 118
default: 137
}
36: aconst_null
37: astore_2
-> creates temporary function that will return wrapped sub-structure.
38: new #46 // class objwrap$wrap$0$
41: dup
42: aload_0
43: getfield #48 // Field _5:Ljava/lang/Object;
46: invokespecial #51 // Method objwrap$wrap$0$."<init>":(Ljava/lang/Object;)V
49: areturn
50: aconst_null
51: astore_3
52: aload_0
.bool - gets the booleanValue
53: getfield #48 // Field _5:Ljava/lang/Object;
56: checkcast #53 // class java/lang/Boolean
59: invokevirtual #57 // Method java/lang/Boolean.booleanValue:()Z
62: ifeq 71
65: getstatic #61 // Field java/lang/Boolean.TRUE:Ljava/lang/Boolean;
68: goto 74
71: getstatic #64 // Field java/lang/Boolean.FALSE:Ljava/lang/Boolean;
74: areturn
75: aconst_null
76: astore 4
78: aload_0
.null? - checks whether it's wrapping null value
79: getfield #48 // Field _5:Ljava/lang/Object;
82: ifnonnull 91
85: getstatic #61 // Field java/lang/Boolean.TRUE:Ljava/lang/Boolean;
88: goto 94
91: getstatic #64 // Field java/lang/Boolean.FALSE:Ljava/lang/Boolean;
94: areturn
95: aconst_null
96: astore 5
98: aload_0
.num - wraps results doublevalue in yeti number
99: getfield #48 // Field _5:Ljava/lang/Object;
102: checkcast #66 // class java/lang/Number
105: invokevirtual #70 // Method java/lang/Number.doubleValue:()D
108: new #72 // class yeti/lang/FloatNum
111: dup_x2
112: dup_x2
113: pop
114: invokespecial #75 // Method yeti/lang/FloatNum."<init>":(D)V
117: areturn
118: aconst_null
119: astore 6
121: aload_0
.str - returns the result as string
122: getfield #48 // Field _5:Ljava/lang/Object;
125: checkcast #44 // class java/lang/String
128: dup
129: ifnonnull 136
132: pop
133: getstatic #81 // Field yeti/lang/Core.UNDEF_STR:Ljava/lang/String;
136: areturn
137: aconst_null
138: areturn
}

It's not having toString now, but the toString support syntax has to be
implemented anyway, so it's not a very good argument.

The variant based code has about same amount of wrapping, when holding the
structure in memory - or more, if its not doing the wrapping on the fly
like the above code does (first generation gc heap allocation of
temporary objects costs near nothing in JVM, but long-lived second
generation is costly).

So efficiency shouldn't be what we are discussing here, it's not hard to
do efficient-enough structure based wrapping (you just have to know to
use getters instead of storing preallocated function closures in the
structure).

Interface is what matters, and the question is which interface is better
and easier to use in the end?

chrisichris

unread,
Oct 3, 2013, 2:05:33 PM10/3/13
to yeti...@googlegroups.com, ma...@cyber.ee
Wow that's nice. Did not know that yeti does such optimisations. Thanks for the detailed answer.

Just to add: beside toString also equals/hashCode need some support 

m = new java.util.HashMap();
m#put("kkk",44);
println (wrap m ==  wrap m); //prints false

Otherwise I think the struct api is indeed better.

Regarding the interpretation of symbols after (->) as string. Yes it would be a special case, but I think that would be fine if your realy see it as sort of "dynamic field access" operator. And - in case - string-literals should be interpreted normally so:

obj -> foo == obj -> "foo" == obj -> 'foo' //the string literal is necessary for not valid symbol names ie obj -> '@strange name'

and for field name expressions:

obj -> "\(expr)" == obj.``->`` expr

Don't know wheter this is possible/useful. Just my thoughts.
  
Thanks,
Christian
> functions. ��

Madis

unread,
Oct 10, 2013, 5:09:36 PM10/10/13
to yeti...@googlegroups.com

On Thu, 3 Oct 2013, chrisichris wrote:

> Wow that's nice. Did not know that yeti does such optimisations. Thanks for
> the detailed answer.
> Just to add: beside toString also equals/hashCode need some support�

You're correct. I have to think some good way for implementing custom
equality, it would be certainly useful. Semantics are pretty
clear - it should act for equality as structure with subset of the
actual fields, but I'm not yet sure about how a good way to implement
this would be.

> m = new java.util.HashMap();
> m#put("kkk",44);
> println (wrap m == �wrap m); //prints false
>
> Otherwise I think the struct api is indeed better.
>
> Regarding the interpretation of symbols after (->) as string. Yes it would
> be a special case, but I think that would be fine if your realy see it as
> sort of "dynamic field access" operator. And - in case - string-literals
> should be interpreted normally so:
>
> obj -> foo == obj -> "foo" == obj -> 'foo' //the string literal is necessary
> for not valid symbol names ie obj -> '@strange name'
>
> and for field name expressions:
>
> obj -> "\(expr)" == obj.``->`` expr
>
> Don't know wheter this is possible/useful. Just my thoughts.

I'll tell when I've had time to properly think it through.

chrisichris

unread,
Oct 11, 2013, 4:49:52 AM10/11/13
to yeti...@googlegroups.com, ma...@cyber.ee


On Thursday, October 10, 2013 11:09:36 PM UTC+2, Madis wrote:


You're correct. I have to think some good way for implementing custom
equality, it would be certainly useful. Semantics are pretty
clear - it should act for equality as structure with subset of the
actual fields, but I'm not yet sure about how a good way to implement
this would be.

 How about a structure which returns a Variant Not ()  | Some v:

toJson v = {
    get str () = if (v is ~Object) instanceof String then 
                        Some (v unsafely_as ~String as string)
                    else
                         Not ()
                    fi,
    ...
}

isA ac v= case v of 
     Not (): failWith "is not value";
     Some x: ac x;
esac;

obj -> "foo" |> isA (.str)

println obj == obj


This is a bit more typing but no compiler/language change necessery.
BTW: why do I have to put braces around field refernces wouldn't it be enough to have just whitespace before the . immdieately follwoed by the fieldname?
 
> m = new java.util.HashMap();
> m#put("kkk",44);
> println (wrap m == �wrap m); //prints false

Madis

unread,
Oct 12, 2013, 5:39:03 AM10/12/13
to yeti...@googlegroups.com
On Fri, 11 Oct 2013, chrisichris wrote:

> On Thursday, October 10, 2013 11:09:36 PM UTC+2, Madis wrote:
>
>
> You're correct. I have to think some good way for implementing
> custom
> equality, it would be certainly useful. Semantics are pretty
> clear - it should act for equality as structure with subset of
> the
> actual fields, but I'm not yet sure about how a good way to
> implement
> this would be.
>
> �How about a structure which returns a Variant Not () �| Some v:

I think I found not-too-disrupting solution to the implementation, had to
add eqName method to the Struct interface. Doesn't mean the compiler can
yet generate such structures by itself, but now it can be later
implemented without changes to the Struct interface - something that
unfortunately is not 100% backwards compatible change (and therefore
better done now than later - I'm _actually_ trying to get the damn
compiler/library into stable enough state to be someday able to call it 1.0).

chrisichris

unread,
Oct 14, 2013, 9:13:15 AM10/14/13
to yeti...@googlegroups.com, ma...@cyber.ee
Thanks. Anyway I am thinking that a property which throws an Exception is something wrong. That's not what is expected and if the (calculated) properties just return values than toString, equals etc should work as well.

BTW why hurry with 1.0 I think yeti runs well as it does and the continues improvement like it is now works well doesn't it?


On Saturday, October 12, 2013 11:39:03 AM UTC+2, Madis wrote:
On Fri, 11 Oct 2013, chrisichris wrote:

> On Thursday, October 10, 2013 11:09:36 PM UTC+2, Madis wrote:
>
>
>       You're correct. I have to think some good way for implementing
>       custom
>       equality, it would be certainly useful. Semantics are pretty
>       clear - it should act for equality as structure with subset of
>       the
>       actual fields, but I'm not yet sure about how a good way to
>       implement
>       this would be.
>
> �How about a structure which returns a Variant Not () �| Some v:

Christian Essl

unread,
Oct 15, 2013, 11:25:19 AM10/15/13
to yeti...@googlegroups.com
Would you mind adding the (->) to the experimental json module. So I can go on and use it that way without my own module:


json value is json_value -> json = {
    get (->) name () =
       case value of
       Object ha: 
           json if name in ha then
                      ha[name]
                  else
                      Null ()
                 fi;
        Null() : value; //for chaining nulls
        _ : failWith "arrow of \(value)"
       esac,
   ....
}   



--
You received this message because you are subscribed to the Google Groups "yeti-lang" group.
To unsubscribe from this group and stop receiving emails from it, send an email to yeti-lang+...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages