Repository navigation
Enforce hash key types at compile time #8893
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
base: master
Are you sure you want to change the base?
Changes from 8 commits
2d5b06c
af171e8
2a37670
7acd11a
6184650
8639817
599b1f9
1ab3ad4
ede59aa
51f66fb
a02b625
f365d32
8b6c2a4
f2f15da
66d581b
4f7680e
4bf7485
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,10 @@ private def run_init_project(skeleton_type, name, author, email, github_name, di | |
| ).run | ||
| end | ||
|
|
||
| private def yaml(value) | ||
| YAML::Any.new(value) | ||
| end | ||
|
|
||
| module Crystal | ||
| describe Init::InitProject do | ||
| it "has proper contents" do | ||
|
|
@@ -146,7 +150,11 @@ module Crystal | |
|
|
||
| with_file "example_app/shard.yml" do |shard_yml| | ||
| parsed = YAML.parse(shard_yml) | ||
| parsed["targets"].should eq({"example_app" => {"main" => "src/example_app.cr"}}) | ||
| parsed["targets"].should eq({ | ||
| yaml("example_app") => { | ||
| yaml("main") => "src/example_app.cr", | ||
| }, | ||
|
Comment on lines
+179
to
+181
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is not very intuitive. What prevents us from not modifying equality at all in this PR?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is, I'd like this change to be gone but still have it compiling and working. |
||
| }) | ||
| end | ||
|
|
||
| with_file "example/.travis.yml" do |travis| | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -179,7 +179,7 @@ describe "Set" do | |
| end | ||
|
|
||
| it "does -" do | ||
| set1 = Set{1, 2, 3, 4, 5} | ||
| set1 = Set(Int32 | Char){1, 2, 3, 4, 5} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It bugs me how strict you need to be in this examples. I imagine that It could happen that this set for example is generated from templates, macros and the explicit typing will be hard to inject. Hence, the type will be the infered from the elements, leading to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, this is a a pretty irritating tradeoff on paper, but let’s think about the overall impact of this consequence. How often is it that you have a homogeneous set that you subtract a heterogeneous set from? Honestly, I can’t think of a single time I’ve ever used a heterogeneous set in application code, let alone in this particular scenario where I’m subtracting one from a homogenous set. To be clear, you don’t need to specify types for all sets. Just ones where the type inference doesn’t get you what you need. I do think that if you’re going to compare values, it’s probably a good idea to know that the types you’re comparing against can be different and the type specifier helps ensure that not only do you recognize that now, but you’ll recognize that when you change the code later. My perspective is that it’s not the same as adding two sets of different types where the result is a union of all the types in both sets. If there’s a bug there it’ll likely be caught elsewhere in the app because of the new type of the resulting set. For example: numbers = Set{1, 2, 3}
another_set = Set{“1”, “2”}
result = numbers - another_set # => Set(Int32){1, 2, 3}
result = numbers + another_set # => Set(Int32 | String){1, 2, 3, “1”, “2”}I don’t think the IMO, the type checker is there to help you. It’s why a lot of people still use static languages. Something that becomes a compile-time error that could be a runtime error is often a good thing. Being able to tell the compiler you know what you’re doing by simply adding a type annotation is also good. All that said, I found a way to let me remove the explicit type specifier. I don’t think I agree with it, but it keeps the same behavior in this scenario.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I guess we should disallow That would need to apply to all collection types. The problem is always equality and inclusion, specially regarding Interestingly, in Ruby: Also: That's because Hash uses But then also in Ruby: but that's because it uses So maybe we can disallow This is a hard problem... :-) We'll need to decide if we want to introduce Maybe we can also disallow Sorry for the wall of text... my point is that this PR can't be thought in isolation will all of these other things. And changing that is a huge breaking change which I'm not sure we can/should do right before 1.0.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In an ideal world, I think that makes sense.
Yeah, that's the catch. 🙂 It's why, even though I disagree with @bcardiff I still made it work without the explicit type annotation. Your focus on consistency is important and I'm glad you called it out. Consistency can often be more important than anyone's perspective of what's "correct" because it allows you to guess at how something works. That's a super power for a programming language. Having to consult documentation all the time gets old quick. I definitely want to have the conversation about whether
I've been using Ruby for 16 years and, honestly, I never understood why Ruby used
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm loving the discussion here. I also use Crystal in large part due to the type-safety and would prefer the A must be a superset of B. I've been bitten by things like this when developing the internals of Lucky where I'm working with data from outside the system (cookies, params, sql). I personally would love a more strict equality with an "escape hatch" of some kind. Maybe that's with You could even do something similar to what we do in Lucky where there is a fallback method that generates a nice little error telling you why it happened and what you can do isntead:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would require something like #6997 (comment): struct Set(T)
def -(other : Enumerable(U)) forall U <= T
# ...
end
@[Deprecated("Ensure *other*'s element type is `T` or a subtype of it")]
def -(other : Enumerable)
dup
end
endThe story is more complex if we want to allow unions of |
||
| set2 = [2, 4, 'a'] | ||
| set3 = set1 - set2 | ||
| set3.should eq(Set{1, 3, 5}) | ||
|
|
@@ -242,7 +242,7 @@ describe "Set" do | |
| end | ||
|
|
||
| it "does subtract" do | ||
| set1 = Set{1, 2, 3, 4, 5} | ||
| set1 = Set(Int32 | Char){1, 2, 3, 4, 5} | ||
| set2 = Set{2, 4, 'a'} | ||
| set1.subtract set2 | ||
| set1.should eq(Set{1, 3, 5}) | ||
|
|
@@ -263,7 +263,7 @@ describe "Set" do | |
| end | ||
|
|
||
| it "does subtract" do | ||
| set1 = Set{1, 2, 3, 4, 5} | ||
| set1 = Set(Int32 | Char){1, 2, 3, 4, 5} | ||
| set2 = [2, 4, 'a'] | ||
| set1.subtract set2 | ||
| set1.should eq(Set{1, 3, 5}) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -470,7 +470,7 @@ module Crystal::Playground | |
|
|
||
| agent_ws = PathWebSocketHandler.new "/agent" do |ws, context| | ||
| match_data = context.request.path.not_nil!.match(/\/(\d+)\/(\d+)$/).not_nil! | ||
| session_key = match_data[1]?.try(&.to_i) | ||
| session_key = match_data[1]?.try(&.to_i) || 0 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this needed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| tag = match_data[2]?.try(&.to_i) | ||
| @logger.info "#{context.request.path} WebSocket connected (session=#{session_key}, tag=#{tag})" | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.