-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement JSON::Any and YAML::Any without recursive aliases #5183
Implement JSON::Any and YAML::Any without recursive aliases #5183
Conversation
I'm a little surprised to see the following changes in specs, isn't data[0] => data.as_a[0]
data["key"] => data.as_h["key"]
data.each {} => data.as_a.each {} I do not dispute the whole change. Removing recursive aliases is probably a good thing. I'm not of fan |
Oh, yes, I forgot to mention that. I decided to remove those, and also remove the Another thing, We could add those convenience methods, though, I'm just not sure it's worth it. |
@asterite because traversing arrays/hashes to get to the part of the data you want, then casting and doing advanced stuff with the result is the 95% usecase. And that's what |
@RX14 If that's the case (it's probably is), we can add those methods back (though I wouldn't make Any an Enumerable) but still implement it without recursive aliases because it seems to be more consistent. |
Loosing enumerable is fine. I still don't see the point to removing recursive aliases. There seems to be no point other than simplifying the compiler, which I don't think is ever a strong enough reason to remove a useful feature itself. |
What about #3158 ? I suggest removing recursive aliases because they are almost exactly the same as implementing them with a struct with an instance variable that accomplishes the recursion. It's not just simplifying the compiler for my benefit, it's simplifying the user experience because they don't have to learn a new complex concept. |
I don't see how #3158 relates to recursive aliases being bad. Nobody in that thread has problems with recursive aliases, just the api of the Any types not offering the option of mapping collections contents to Any. |
Exactly. But to offer that kind of API we need to do |
That's an argument for implementing JSON::Any by parsing into JSON::Any directly instead of JSON::Type, not for removing recursive aliases for other usecases, or for removing the old recursive JSON::Type. |
@RX14 You probably read this already, but I'd like to share this comment about Nim: https://news.ycombinator.com/item?id=15577922 To be honest, I read the Nim manual some time ago but was baffled with it, as there's so many things you can do, and so many ways to do it, that whenever I want to start doing something it's hard. That probably also means there are more efficient ways to do some things than in Crystal, but it comes at the cost of confusing the user. For experts, it's fine, though. That's why Go, despite not being liked because it lacks generics, Once again, I'm glad we removed aliases in methods: I would sometimes like to have these aliases back like in Ruby for greater flexibility ( Regarding recursive aliases, or any feature, I always like to imagine what if we never had that feature in the first place. Imagine when me and @waj tried to implement JSON, instead of thinking "we need a recursive type, let's add recursive aliases to the language" we had said: "we need a recursive type, we can implement this with a struct and an instance variable". Recursive aliases would have never existed then. The question to ask is: would the API or user experience had been dramatically different? I don't think so. In fact, the user experience is probably slightly better regarding casting and consistency. It might be a bit inconvenient to do So, without recursive aliases there are less ways to do things with more or less the same outcome. When a user is faced with this problem, it's easy: you use a struct/class with an instance variable that does the recursion (or just use Anyway, many ways to say the same thing 😊 |
@asterite thank you for your comment, I think you've managed to convince me. That also begs the question, what other features should be simplified or removed? When adding new features, how will we maintain this ethos? Is this kind of simplicity already a core tenet of crystal or does removing recursive aliases represent a shift in the goals of the language (and is that a good thing)? I don't know the answer to these questions and I think anyone does, but I think that the general design principles of crystal need to be distilled and codified. So that's why I think we should make the Any types not use recursive aliases, as that's something we can do right now and is a sensible change to make whether recursive aliases stay or not. But I don't think we should remove recursive aliases right now, or even remove JSON::Type in its current form, until we have a bit more discussion. |
This change about JSON and YAML is good and should be merged, regardless of the fate of recursive aliases.
|
Bump: any updates? |
We can go forward with this. But if we do, we should remove recursive aliases from the language. That's the whole point of this PR, to prove that they aren't a necessary feature. Should we? |
@asterite as other have voiced, I think this change should be merged and the fate of recursive aliases be dealt separately. I think this is the necessary and first step in prove that: if Crystal's own codebase don't use recursive aliases, then it makes sense to remove it. There are some UX points in #5155 that I think will be resolved by more usage of this feature. Cheers. |
@luislavena Thanks for the feedback! Sounds good. Someone (one or two) should approve this and then we can merge this. |
With the example "How to recursively change Hash values and save each change to new Hash" |
Related to #5155
(this PR isn't finished, but it's working well)
So, I managed to implement
JSON.parse
andYAML.parse
without recursive aliases.JSON.parse
still returnsJSON::Any
, but this is a struct whose raw value contains all possible types such asNil
,String
orArray(JSON::Any)
, so an array of these same structs - that's how we achieve recursion without recursive aliases (instead of it beingArray(JSON::Type)
, which doesn't exist anymore).Some things I noticed when doing this:
In YAML a
Hash
(mapping) can have keys of any YAML type, not just strings. With recursive aliases, the key is simply aYAML::Type
, and because it's a union it can be anInt64
or aString
, for example. Without recursive aliases the key is aYAML::Any
, which isn't a union. So, when you index aHash(YAML::Any, YAML::Any)
with aString
, that won't work out of the box, unless we make comparisons betweenString
andYAML::Any
work both ways, and theirhash
values are the same. This is just a matter of reopening these types and overloading==
, but it still a bit surprising. In any case, it was a bit surprising before becauseYAML::Any==(String)
would work well butString==(YAML::Any)
wouldn't, so that was necessary even with recursive aliases.The overall API ends up being nicer and more consistent in my opinion.
For example, #3158 won't exist. For example this works just fine now:
Trying to do the same with the existing code we get:
Super inconsistent. At that point we have to do
JSON::Any.new(first).as_h
orfirst.as(Hash)
. Why in one place we can useas_a
and in another place we can use.as(Hash)
.So maybe we just get rid of
JSON::Any
and use the recursive definition? We'd have to cast withas
every time we want to use a value, but at least it would be consistent.Unfortunately, that will work, but if we want, for example, to include a property in
JSON.mapping
that can be of any JSON type (so basically, just parse it to the recursive type) we can't do it with a recursive alias because recursive aliases can't have methods. We need to provide anew(JSON::PullParser)
method and that's impossible to do. Or introduce a type that has this method (basicallyJSON::Any
) but it's just simpler to have one type and not two.I still think that doing it this way is the best way:
as_a
,as_h
, etc. instead of.as(Array)
,.as(Hash)
I also think that removing recursive aliases will make it much easier to implement generic aliases because we don't have to deal with recursive generic aliases.
In any case, I don't think this will get merged before 0.24.0, and even after I'll have to discuss it with the team at Manas.