Is there any way to express the empty hash in Sorbet?
I have the attribute that has a well defined shape or is set to {}. Obviously T.type_alias({}) won't work because {} works as the Hash which is translated to T::Hash[T.untyped, T.untyped]. The easiest solution would be to have something like T.nil and then use it as T::Hash[String, T.nil] (fetching the value for the non-existing key will always return a nil value), but there is no such construction.
The workaround is to change the code to not accept the empty hash there and handle it differently. Then, it is possible to have T.nilable(SomeType). However, I would like to keep the code as it is now and just add a proper type signature.
I think you could use T::Hash[String, T.nilable(String)]? However when I try on sorbet.run, it seems to indicate that it won't type-check arguments of the method correctly.
I have the attribute that has a well defined shape
You may want to use T::Struct to represent this. Sorbet doesn't have great support for shape yet.
T.nil
You can use NilClass. It's actually a Ruby class.
Related
(Note that this isn't reproducible on sorbet.run, it's only reproducible with a local copy of Sorbet as far as I can tell)
I was hoping I could use the Typed Structs feature to create a method signature where one of the parameters is an options hash, but this doesn't work:
# typed: true
require 'sorbet-runtime'
extend T::Sig
class OptionsStruct < T::Struct
prop :x, Integer, default: 1
end
sig { params(options: OptionsStruct).void }
def method(options)
puts options.x
end
# This works
method(OptionsStruct.new({x: 2}))
# This causes the typechecker to throw.
method({x: 2})
Essentially, when you typecheck this file it complains about passing a hash in, when a Struct is expected. My question is: how can I define a valid signature for a hash that has specific parameters? Structs clearly aren't working here. While I haven't tried Shapes, according to the docs they're very limited, so I'd prefer not to use them if possible.
The documentation on generics mentions Hashes but seems to suggest they can only be used if the hash's keys and values are all of the same types (e.g. Hash<Symbol, String> requires that all keys be Symbols and all values be Strings), and doesn't provide any way (as far as I know) to define a hash with specific keys.
Thanks!
Essentially, you have to choose to go one of various ways, (three which you've already mentioned):
Use a T::Hash[KeyType, ValueType]. This allows you to use the {} syntax when calling a method that takes it as a param, but forces you to use the same type of key and value for every entry.
Use a T::Hash[KeyType, Object]. This is a bit more flexible on the type of the value... but you loose type information.
Use a T::Hash[KeyType, T.any(Type1, Type2, ...). This is a middle ground between 1 and 2.
Use shapes. As the docs say, the functionality might change and are experimental. It's the nicest way to model something like this without imposing the use of the T::Struct to the caller:
sig { params(options: {x: Integer}).void }
def method(options)
puts options[:x]
end
Use a T::Struct, like you did. This forces you to call the method with MyStruct.new(prop1: x, prop2: y, ...)
All of them are valid, with 4 and 5 being the ones that give you the most type safety. Of the two, 4 is the most flexible on the caller, but 5 is the one that you know Sorbet is not going to change support in the short/medium term.
We have do-and-replace functions like map!, reject!, reverse!, rotate!. Also we have binary operations in short form like +=, -=.
Do we have something for mathematical round? We need to use a = a.round, and it's a bit weird for me to repeat the variable name. Do you know how to shorten it?
OK, smart guys have already explained, why there is no syntactic sugar for Float#round. Just out of curiosity I’m gonna show, how you might implement this sugar yourself [partially]. Since Float class has no ~# method defined, and you do rounding quite often, you might monkeypatch Float class:
class Float
def ~#
self.round # self is redundant, left just for clarity
end
end
or, in this simple case, just (credits to #sawa):
alias_method :~#, :round
and now:
~5.2
#⇒ 5
a = 2.45 && ~a
#⇒ 2
Since Numerics are immutable, it’s still impossible to modify it inplace, but the above might save you four keyboard hits per rounding.
As for destructive methods, it is impossible since numerals are immutable, and it would not make sense. Would you want a numeral 5.2 that behaves as 5?
As for syntax sugar, it would be a mess if every single method had one. So there isn't. And since syntax sugar is defined in the core level, you cannot do anything in an ordinary Ruby script to create a new one.
Ruby's numeric types are immutable: they are value objects. Therefore you won't find any methods that mutate a number in place.
Because the numeric types are immutable, certain optimizations are possible that would not be possible with mutable numbers. In c-ruby, for example, a reference, which may point to any kind of object, is normally a pointer to an object. But if the reference is to a Fixnum, then the reference contains the integer itself, rather than pointing to an instance of Fixnum. Ruby does a number of magic tricks to hide this optimization, making it appear that an integer really is an instance of a Fixnum.
To make numbers mutable would make this optimization impossible, so I don't expect that Ruby will ever have mutable numeric types.
Let's say we have a Ruby method like this:
# Pseudocode
def get(globbed)
a_items = Dir.glob(globbed)
a_items.length == 1 ? a_items.first : a_items
end
The method is meant to return a String containing information about the items in question. If there are many items, it will return an Array. The ternary makes it so that if there is only one item, it just returns that String.
What is the best practice here? Should such a method always return an Array even if there is only one item?
It should always return an array. Returning different things means that whatever method that calls this method would also have to have a condition. That is not good. Whenever you can get rid of a condition, you should. A condition should only be used as a last resort.
As a real example, the jQuery library built on top of JavaScript has the notion of selectors, expressed in the form $(...). This can result in multiple matching dom objects, or a single one. But jQuery always returns an array even if the matched dom object is one. That makes things simple.
It's always about use cases. You have to define what's the responsibility of that method and then decide what makes sense to do.
In this specific case, I would say that, unless there isn't any specific reason to return different types, you should choose the way that is simpler, both to test and to read.
Always returning an array in this case means clearer method interface:
"The method returns an array with the directory content"
instead of the more convoluted
"The method returns an array of directory content if there more than
one object, otherwise return the single object."
So, clarity first of all.
And: testing would result easier. The cyclomatic complexity of the routine is less.
There are cases where the uniformity of return types can't be fulfilled. Just think of the Array method index: it wouldn't be possible to distinguish between "object not found" and "index 0" if the practice here was applied.
Conclusion: here I don't see any reason why to make the method more complex by distinguishing the two cases, so.. KISS.
Hi, ruby provides block, yield and iterator to permit easy array or hash treatment. And it's a good practice to use the same code for one or several numbers of element. Exemple :
a_items.each { |element| file_treatment(element) }
Regards.
For example, I've checked the documentation on certain methods, like sort. And it looks like the only difference between .sort and .sort! is that one sorts self in place and the other returns an array. I'm a little unclear on what that means - they seem to effectively do the same thing.
Can anyone help me understand this a little better?
When to Use Bang Methods
Technically, the exclamation point (or bang) doesn't intrinsically mean anything. It's simply an allowable character in the method name. In practice, however, so-called bang methods generally:
Changed objects in-place. For example, #sort! sorts self in place, while #sort returns a new array created by sorting self.
Some bang methods return nil if no changes were made, which can cause problems with method chains. For example:
'foo'.sub 'x', 'y'
# => "foo"
'foo'.sub! 'x', 'y'
#=> nil
Use bang methods when you want to mark a method as creating notable side effects, producing destructive operations, or otherwise requiring additional caution or attention. This is largely by convention, though, and you could make all your methods bang methods if you were so inclined.
Methods with a bang(!) are meant to signify a little more caution is required. So, either modification in place vs. not in place (if you are modifying the object in place - you better be sure that you really want to), or in other cases like find_by and find_by! (see here) where one causes an exception if no record is found and one doesn't cause an exception.
Can you guess which one does and which one does not cause an exception?
The methods with the exclamation point alter the actual object they're called on, where as the methods without will just return a new object that has been manipulated.
i.e.
pizza = 'pepperoni'
pizza.capitalize
Now the pizza variable will still equal 'pepperoni'.
If we then call
pizza.capitalize!
The pizza variable will now equal 'Pepperoni'
So as I understand it, the === operator tests to see if the RHS object is a member of the LHS object. That makes sense. But how does this work in Ruby? I'm looking at the Ruby docs and I only see === defined in Object, I don't see it in Integer itself. Is it just not documented?
Integer is a class, which (at least in Ruby) means that it is just a boring old normal object like any other object, which just happens to be an instance of the Class class (instead of, say, Object or String or MyWhateverFoo).
Class in turn is a subclass of Module (although arguably it shouldn't be, because it violates the Liskov Substition Principle, but that is a discussion for another forum, and is also a dead horse that has already been beaten many many times). And in Module#=== you will find the definition you are looking for, which Class inherits from Module and instances of Class (like Integer) understand.
Module#=== is basically defined symmetric to Object#kind_of?, it returns true if its argument is an instance of itself. So, 3 is an instance of Integer, therefore Integer === 3 returns true, just as 3.kind_of?(Integer) would.
So as I understand it, the === operator tests to see if the RHS object is a member of the LHS object.
Not necessarily. === is a method, just like any other method. It does whatever I want it to do. And in some cases the "is member of" analogy breaks down. In this case it is already pretty hard to swallow. If you are a hardcore type theory freak, then viewing a type as a set and instances of that type as members of a set is totally natural. And of course for Array and Hash the definition of "member" is also obvious.
But what about Regexp? Again, if you are formal languages buff and know your Chomsky backwards, then interpreting a Regexp as an infinite set of words and Strings as members of that set feels completely natural, but if not, then it sounds kind of weird.
So far, I have failed to come up with a concise description of precisely what === means. In fact, I haven't even come up with a good name for it. It is usually called the triple equals operator, threequals operator or case equality operator, but I strongly dislike those names, because it has absolutely nothing to do with equality.
So, what does it do? The best I have come up with is: imagine you are making a table, and one of the column headers is Integer. Would it make sense to write 3 in that column? If one of the column headers is /ab*a/, would it make sense to write 'abbbba' in that column?
Based on that definition, it could be called the subsumption operator, but that's even worse than the other examples ...
It's defined on Module, which Class is a subclass of, which Integer is an instance of.
In other words, when you run Integer === 3, you're calling '===' (with the parameter 3) on the object referred to to by the constant Integer, which is an instance of the class named Class. Since Class is a subclass of Module and doesn't define its own ===, you get the implementation of === defined on Module.
See the API docs for Module for more information.
Umm, Integer is a subclass of Object.