> 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.
> 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).
>> // 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.
> 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.
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
- 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
> 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.
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
> 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.
> 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.
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.
> 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. ;)
> 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.
----
class JsonError(String error) extends Exception(error) endtypedef 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)";
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?�
--
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.
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)
> functions. ��
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
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:
--
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.