The following doesn’t compile:
class H < Hash(String, H)
end
p H.new
The following doesn’t compile:
class H < Hash(String, H)
end
p H.new
What’s the use case here? You don’t really need to create your own class as Hash
already supports nested types.
class MyClass
property name : String = "Jim"
end
hash = Hash(String, Hash(Symbol | String, Int32 | Bool | MyClass)).new
hash["foo"] = {:bar => 123, :far => true, "klass" => MyClass.new}
hash # => {"foo" => {:bar => 123, :far => true, "klass" => #<MyClass:0x55e340f73f00 @name="Jim">}}
I need it nested to N depth. N is only known at runtime based on the data loaded.
Each Hash
at each nested depth has additional properties in addition to the Hash
itself.
There are also additional methods I didn’t include in the example that should not exist on the Hash
class.
I mean i suppose you could do like:
class CustomHash(K, V) < Hash(String, V)
end
CustomHash{"foo" => "bar"}
Although this would of course include the methods from Hash
. I’m assuming its not possible to new up objects instead of using hashes? Might be harder to work with as the types of everything is just going to be a big union.
Another option would be defining a recursive alias, then use that as the type restriction on your own class.
alias MyCustomHash = ...
class MyClass
def initialize(@hash : MyCustomHash); end
end
But a more OO approach would be better imo.
You can use a recursive alias. But I think you don’t need to use hashes in your program.
Its easier if you tell us what you want to do, what’s your problem (not your solution).
You may also take a look at what’s going on with JSON and YAML, that both give you an arbitrary tree in runtime.
Parsing a sysctl like structure with additional data.
foo.bar.int = 1
foo.bar.str = "foo"
foo.bar.baz.int = 1
I need dig like functions and iteration at any point. A nested set of hashes with additional properties seemed like the closest data structure.
hash.dig["foo", "bar"].each
You can always do something like this: https://play.crystal-lang.org/#/r/74qu
Yes, that’s what JSON::Any#dig does (https://crystal-lang.org/api/0.29.0/JSON/Any.html#dig(key%3AString|Int%2C*subkeys)-instance-method)
The problem may arise if you want a key to have a value and a subtree at the same time:
foo.bar = "value"
foo.bar.baz = "another value"
One way around would be a notion of an empty key, so that becomes something like
foo.bar = Subtree.new
foo.bar.<empty> = "value"
foo.bar.baz = "another value"
Also, an often overlooked way to solve this is to flatten the tree into a single level hash, bash-style:
tree["foo.bar.int"] = 1
tree["foo.bar.str"] = "foo"
tree["foo.bar.baz.int"] = 1
As long as you are fine with the drawbacks it’s perfectly legitimate.
Got it. Thank you.
Example: Carcin
test = Hash(String, Hash(String, Hash(String, String))).new
test["test"] = {"one" => {"one" => "test"}}
pp test
Just replace Hash(String, String)
with the last String
, dependent on how many you want nested.
This is the first time Didactic.Drunk has posted — let’s welcome them to our community!
By the way, welcome to the community!!
In Perl language code, nested Hash is very easily to create and use,like this:
#!/usr/bin/perl -w
my %h;
$h{"A"}{"B"}{"C"} = 1;
# this is depth=3 netst hash, can use any N depth hash easily in Perl.
So sometimes I also want to use nested Hash Type easily in Crystal.
it do works! thanks~
This works for me:
$ crystal eval --time --progress --error-trace 'h=Hash{"one" => 1, "two" => Hash{"foo" => 3}}; printf %[debug: >#{h}< >%s< >%s< >%s<\n], h["one"], h["two"], h["two"];'
debug: >{"one" => 1, "two" => {"foo" => 3}}< >1< >{"foo" => 3}< >{"foo" => 3}<
Execute: 00:00:00.012389834
But this fails:
$ crystal eval --time --progress --error-trace 'h=Hash{"one" => 1, "two" => Hash{"foo" => 3}}; printf %[debug: >#{h}< >%s< >%s< >%s<\n], h["one"], h["two"], h["two"]["foo"];'
error in line 1 (main)
Error: undefined method '[]' for Int32 (compile-time type is (Hash(String, Int32) | Int32))
What is the correct syntax to access the nested value of key "foo"
?
In Perl this would be $h{two}{foo}
.
After some playing I got this to work:
$ crystal eval --time --progress --error-trace 'h=Hash{"one" => 1, "two" => Hash{"foo" => 3}}; printf %[debug: >#{h}< >%s< >%s< >%s<\n], h["one"], h["two"], h["two"].as(Hash)["foo"];'
debug: >{"one" => 1, "two" => {"foo" => 3}}< >1< >{"foo" => 3}< >3<
Execute: 00:00:00.010882643
But it looks way less compact and efficient syntax like Perl. Is there a better way?
For your real usecase almost certainly. Giving good advice on such a synthetic example is very hard though unfortunately. See http://xyproblem.info/
Update: I asked in Crystal gitter chat [1] too and got answers which I’m posting here just to capture the info for other newbies:
So in Perl $h{two}{foo}
is very compact and not so compact in Crystal in my above example h["two"].as(Hash)["foo"]
.
However, I learned that if the nested hash has consistent values then Crystal can also do the compact syntax, e.g. h["two"]["foo"]
:
$ crystal eval --time --progress --error-trace 'h=Hash{"two" => Hash{"foo" => 3}}; printf %[debug: >#{h}< >%s< >%s<\n], h["two"], h["two"]["foo"];'
debug: >{"two" => {"foo" => 3}}< >{"foo" => 3}< >3<
Execute: 00:00:00.012247333
Further I discovered that Crystal Named Tuples might be used instead of the second level hash key as an alternative compact syntax h["two"].foo
which is probably also faster in performance, e.g.:
$ crystal eval --time --progress --error-trace 'record A, foo : Int32; h=Hash{"two" => A.new(foo: 3), "three" => A.new(foo: 4)}; printf %[debug: >#{h}< >%s< >%s<\n], h["two"], h["two"].foo; p! h;'
debug: >{"two" => A(@foo=3), "three" => A(@foo=4)}< >A(@foo=3)< >3<
h # => {"two" => A(@foo=3), "three" => A(@foo=4)}
Execute: 00:00:00.014652016
However, again, if you mix hash value types, even with named tuples, then the syntax gets fat again h["two"].as(NamedTuple)[:foo]
, e.g.:
$ crystal eval --time --progress --error-trace 'h=Hash{"two" => {foo: 3}, "three" => {foo: 4, bar: "baz"}, "four" => 1}; printf %[debug: >#{h}< >%s< >%s<\n], h["two"], h["two"].as(NamedTuple)[:foo]; p! h;'
debug: >{"two" => {foo: 3}, "three" => {foo: 4, bar: "baz"}, "four" => 1}< >{foo: 3}< >3<
h # => {"two" => {foo: 3}, "three" => {foo: 4, bar: "baz"}, "four" => 1}
Execute: 00:00:00.010379377