-
Notifications
You must be signed in to change notification settings - Fork 1
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
Add option to allow positional arguments #4
base: master
Are you sure you want to change the base?
Conversation
You could also allow both positional and named arguments by default, then you wouldn't need to add this additional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great, thanks for your contribution! It's nice to have this more inline with Struct
- though it's almost a shame that keyword_init
would now work the other way 🤔
Would you mind adding some examples of this to the README.md too?
And afterwards I think I'll be able to approve and release 👌🏻
spec/typed_struct_spec.rb
Outdated
it "is less verbose" do | ||
UserId1 = TypedStruct.new({keyword_init: false}, identifier: Integer) | ||
UserId2 = Struct.new(:identifier, keyword_init: true) | ||
id1 = UserId1.new(2) | ||
id2 = UserId2.new(identifier: 2) | ||
expect(id1).to have_attributes id2.to_h | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the spirit of being less verbose, I wonder if it would be helpful to have a new class whose default is keyword_init: false
(in the same way that Struct normally is too). On the other hand, perhaps it wouldn't bring that much, because you only have to write your struct definition once.
@@ -6,20 +6,20 @@ | |||
end | |||
|
|||
context "on overriding native methods" do | |||
before { $stdout = StringIO.new } | |||
before { $stderr = StringIO.new } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this tidying up, this is great!
@@ -33,7 +33,7 @@ | |||
context "when passing options" do | |||
it "allows for default values" do | |||
x = TypedStruct.new( | |||
TypedStruct::Options.new(default: 5), | |||
TypedStruct::Options.new(default: 5, keyword_init: true), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind adding another spec without the keyword_init
, to confirm that it is keyword_init
by default?
Thanks for reviewing! So I thought about it some more:
Which says that in the upcoming Ruby 3.2.0 release the default struct behaviour will be changed to allow keyword arguments. On the feature page I see they've done something similar to my idea of using a symbol, where they now permit three options instead of two:
By default it looks like you'll then be able to use either keyword arguments or positional arguments - not mixed like how I did it in this PR (which I never had a use for anyway). So as far as I'm concerned the ruby team has already done all the work for us here in terms of how the interface should look and ensuring backwards compatibility. |
Those are good considerations, thanks! I'm glad that this will be more in keepiing with IIRC it was 2.7.1 which made the changes to kwargs? With #5 I just added CI tests for this, with ruby versions 2.6 - 3.1, so perhaps you could rebase onto master and try it out there? I also dropped ruby 2.4 from the minimum ruby version in the gemspec (which I believe was the default on creation), because rbs doesn't support <2.6 anyway - so that's something at least |
f30da96
to
1cc6480
Compare
Ok so what I've done, is copy the ruby test file for Struct and modify it to work with TypedStruct. It was mostly just a matter of adding |
spec/test_typedstruct.rb
Outdated
# This file is copied from the ruby test suite and modified for this gem: | ||
# https://github.com/ruby/ruby/blob/master/test/ruby/test_struct.rb | ||
# Associated helper files under /vendor directory are from: | ||
# https://github.com/ruby/ruby/tree/master/tool/lib |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update! I have to say though, I don't love having these copied over from another code base, especially with everything else from the test helpers - though I understand how it could be helpful, so I'm not sure what the best solution is here.
Looking at the tests themselves, a lot of them seem to be testing some of Struct's basic methods that this gem doesn't [deliberately] touch.
Just thinking out loud here, but perhaps something like this could be a simple option of comparing functionality?
RSpec.describe TypedStruct do
# maybe it could be helpful to more complexity here, but not sure
let!(:struct) { Struct.new(:a, :b, :c) }
let!(:typed_struct) { TypedStruct.new(a: Rbs("untyped"), b: Rbs("untyped"), c: Rbs("untyped")) }
it "has the same singleton methods as a normal struct" do
struct.methods.each do |method|
expect(struct.send(method)).to eq typed_struct.send(method)
end
end
it "has the same behaviour as a normal struct" do
str = struct.new(1, 2, 3)
ts = typed_struct.new(a: 1, b: 2, c: 3)
str.methods.each do |method|
expect(str.send(method)).to eq ts.send(method)
end
end
end
Though obviously not everything would have zero arity and there may be some methods which we clearly be different eg. class
🤷🏻 (struct.methods - Object.methods).select { |m| struct.method(m).arity.zero? }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But in addition, I wouldn't mind so much if compatibility is completely exact just at the moment (as long as it is always trending upwards over time), given the relatively early stage of this gem (pre 1.0)
Yea it's probably impossible to get full compatibility too, since much of the struct interface is implemented in C. $ irb
3.2.0-preview2 :001 > Struct.new(:test).methods(false)
=> [:new, :[], :members, :inspect, :keyword_init?]
3.2.0-preview2 :002 > Struct.instance_methods(false)
=> [:hash, :deconstruct_keys, :==, :members, :to_a, :to_s, :[], :[]=, :values_at, :to_h, :eql?, :select, :filter, :pretty_print_cycle, :deconstruct, :inspect, :each_pair, :dig, :pretty_print, :values, :length, :size, :each] I added two |
So as to avoid mixing test stuff with code, I added a |
4837c6c
to
e25d3e3
Compare
Ruby 3.2.0 released! Seems to work fine with this PR. |
I like making wrappers like
PrimaryKey[1]
/UserId[5]
just for type safety: the classic example being extracting from two database columns and wanting to avoid mixing up an integer from one column with another.In these cases it's unnecessary to write out the field name, as that information is included in the type name.
This makes the interface even more similar to
Struct
.While I was at it, I found it's also possible to support both positional and keyword arguments at once. I don't have a use case for that, but I included it anyway ¯\_(ツ)_/¯
I borrowed the
keyword_init
option name from theStruct
option of the same name - here it works in reverse - the default istrue
but you can manually set it tofalse
to enable positional arguments.