powing
(Albert)
1
Is it possible to set a function parameter so that an integer number entered is within a certain range i.e. between zero and fifteen. Much like making the parameter type safe, only a step further so that it has an upper limit ?
This sounds like an ideal use for an integer based enum.
lukasa
(Cory Benfield)
3
One cannot do this trivially when accepting Int. This is because the problem you’re expressing (“must be within the range of zero and fifteen”) is currently expressed in the value domain, not the type domain.
You can transform this by forcing the transformation of the Int into a different type that does provide that constraint. @Joanna_Carter has suggested one alternative, but here is mine:
struct ZeroToFifteenInt: RawRepresentable, ExpressibleByIntegerLiteral {
var rawValue: Int {
didSet {
precondition((0...15).contains(newValue))
}
}
init(rawValue: Int) {
precondition((0...15).contains(rawValue))
self.rawValue = rawValue
}
init(integerLiteral: Int) {
self = .init(rawValue: integerLiteral)
}
}
2 Likes
Unfortunately, this can fail at runtime.
Here is the enum idea, which is safe :
enum ZeroToFifteen: Int
{
case zero = 0
case one
case two
case three
case four
case five
case six
case seven
case eight
case nine
case ten
case eleven
case twelve
case thirteen
case fourteen
case fifteen
}
func testZeroToFifteen(value: ZeroToFifteen)
{
let intValue = value.rawValue
// …
}
{
testFifteen(value: .seven)
}
lukasa
(Cory Benfield)
5
It can fail at runtime because I wrote it that way, but the same is true in general if you have to transform between Int and your other type. It is not possible to transform an Int into another type without dealing with the situation where the Int is out of range.
Mine can't fail at runtime if you check for the result of the failable initialiser.
if let test = ZeroToFifteen(rawValue: 16)
{
print(test) // never gets executed
}
glessard
(Guillaume Lessard)
7
That’s just choosing the type of failure you prefer. You are using a faisable initializer, which by definition can fail.
lukasa
(Cory Benfield)
8
@glessard is right. Transforming my version to yours is trivial: replace the preconditionFailure with an init? and all is well again. The two approaches are not meaningfully different in what they can express (though they are different in their runtime layout).
Karl
(👑🦆)
9
This is the key, and why I’d rather go for your version personally (with an appropriate failure mode for the context where it will be used).
One approach (the wrapper struct) is an Int that has been validated to be within a specific range; the other is an enum which is representable as an Int. It’s a subtle difference, but I’d guess that the former is friendlier for the optimiser.