In a pure naming sense, yes.
I'm neutral on the pitch, but from a naming perspective I would prefer ifSome
instead of ifLet
.
Iâm fine with either of those, but definitely like for less than if. I think I like ifLet
for this.
This should be unwrap
, to match the existing terminology used for optionals. ifLet
and the like make no sense from a Swift naming perspective.
Additionally, we should have get
, which can throw, to match Result
.
I think this has merit as a standard language / library inclusion vs either use of map
or piecemeal wrappers thereupon.
map
is not logically appropriate in my mind - it is a perversion of its intent to use it for this purpose, and though I have seen it used this way in the wild, every time I do it adds cognitive burden to me as the reader while I 'handle the exception' that it's not actually being used to map anything. This is entirely subjective and not a consensus perspective, but it is not an uncommon one either. There is the semantic interpretation that you're mapping the lambda to the values, not values to other values, but for whatever reason that doesn't fit as well for me. I also think it enhances readability, in any case, to make clearer your intent between "I am transforming this collection of values into another collection of values" vs "I am doing something with all the values in this collection". i.e. map
vs forEach
on Array
.
The clearer alternative (if let âŚ
) is unduly verbose for such a frequent and conceptually simple operation. If nothing else I feel like it warrants some syntactic sugar.
I do find ifLet
a little unattractive, though, as it seems to be making an explicit reference to the if let âŚ
syntax - while not actually following any pattern of said syntax - syntax which is incidental to what you're trying to do anyway, and understanding any parallels or history from the if let âŚ
syntax is not useful to understanding what you're trying to do.
ifPresent
, ifSome
, ifSet
, etc are much more to the point. But, some are verbose:
number.ifPresent { print(number) }
âŚis barely shorter than the existing:
if let number = number { print(number) }
Though the former is clearly cleaner (fewer repetitions of 'number', and no introduction of technically a distinct, possibly shadowing variable).
Just exploring the solution space, there's also the option of more deliberately syntactic sugar / compression for the if let âŚ
syntax, something essentially like:
if number? { print(number) }
(exact syntax might need some fiddling to fit in with existing family of & use of '?' operatorsâŚ)
I also mulled over variations that use some kind of operator on 'number' to indicate that the enclosing code (e.g. 'print') should not be invoked if 'number' is nil, but every syntax I've come up with is a bit too 'spooky action at a distance'. It seems only natural to have the condition precede the action and be stated clearly (especially if you have non-trivial examples where the 'skip this whole statement if this value is optional' might be visually buried deep in the statement).
It does feel like an awkward asymmetry that we have the very nice '?.' operator for member functions yet free functions have nothing.
Or perhaps the way optionals are fundamentally treated should be changed, such that you can do:
assert(number != nil)
print(number) // 'number' is implicitly unwrapped, by virtue of the compiler knowing it cannot be nil at this point.
And:
if number != nil and otherNumber != nil {
// 'number' and 'otherNumber' are implicitly unwrapped in this context.
}
And:
guard number != nil and otherNumber != nil else {
// Bewm.
}
Optionals from the compiler's perspective are just a contract that it needs to enforce, and it currently requires a lot more boilerplate from the humans than strictly necessary. IMO the only significant question is whether humans will be unreasonably confused by a less explicit system. I for one don't think so - the above reads much like most other C-family languages, merely with the distinction that the Swift compiler is more careful that we humans don't screw up by missing an assertion, conditional, guard, or similar.
Or maybe reuse of the try
syntax family? e.g.:
func doAllTheThings(_ a: Int, _ b: String) { ... }
let maybeString: String? = ...
doAllTheThings(5, maybeString) // Compiler raises error as normal.
try? doAllTheThings(5, maybeString) // Compiler allows this.
I feel like the practical concern with that is that, by adding more things that try
sort of does, you could end up with individual statements where that try
is covering for many different things - exceptions, unwraps, etc. Having them conflated isn't necessarily always your intent (though it might be - sometimes you really do just want to say "try to do this thing and if it doesn't happen for any reason, that's fine", for which this is great). Though you always could decouple them if you wanted, using existing if let ...
syntax to handle optional unwrapping separately.
OK. I must disagree. Optional Binding is itself syntactic sugar, not a logical concept, thus not really serving as a valid reason to follow it.
Furthermore, we aren't really binding anything are we?
And lastly, if ifLet(_:)
why not ifVar(_:)
- Both are considered bindings. Preferring the former over the latter simply makes no sense.
Although I donât mind ifSome
and ifLet
for their similarity to forEach
I feel equally good about unwrap
as the spelling.
I disagree about needing a get
which throws on Optional
, though. I think a reasonable way to semantically differentiate Optional
and Result
is to talk about âexists or notâ vs. âright or wrong.â If you need to represent âexists or throwsâ then it makes more sense to me to create a Result
from your Optional
and use Result
âs throwing accessor.
We shouldnât need to create an intermediate Result
just to immediately call get
.
Syntax is syntax, whether sugared or salted. Syntactic sugar is the reason in the first place that optionals in Swift aren't as cumbersome as it would have been if Swift would have been a strictly logical language.
In Kotlin, the spelling is simply .let{âŚ}
, but since let
is already a keyword in Swift, we may want to stay away from that.
It cannot know that it cannot be nil, at most it can assume that it's the case
var _number: Int? = 5
var number: Int? {
defer { _number = nil }
return _number
}
assert(number != nil)
print(number) // will print nil in the current swift version
I don't really see the point in a throwing .get
method, since it would conceivably only ever throw one error. In such cases, Optional
is exactly what we need rather than do ⌠try ⌠catch ⌠evaluate any kind of error
.
Mind you, a throwing accessor would compose better with other parts of a do
block, just like Optional.map
does with map
and flatMap
chains now.
This feels like a feasible route to me. Switch cases already have similar sugar for optionals, e.g.:
switch optional {
case value?: print("The unwrapped value is: \(value)")
default: fatalError("No value!")
}
So we could have if number? { print(number) }
Or, alternatively: if case number? { print($0) }
Precisely, a throwing accessor makes using options in an error context painless, and itâs something I eventually add to every project. We can customize the error is a variety of ways, as this has been discussed before.
It is not much shorter, but it would all be supplied by code completion, where the existing alternative would not.
Looks nice, and is very consistent with Swift's switch
syntax but:
- We aren't gaining much here in terms of characters won
- Moving to a conditional statement kind of defeats the point of keeping it lightweight
- Who really writes an
if-statement
on a single line?
I like this idea, people definitely shouldnât be using map for this. The purpose of map is the exact opposite of this method. Map takes a function that should ha no side effects, and you use it because you want to access the transformed value. This function here would be used for its side effects, and would not return anything. Completely different in other words, just like forEach.
When it comes to naming, let or ifLet make no sense at all, since let is used for binding things and nothing is bound here.
ForEach is not a bad idea, nor is forValue or forSome.
This is not actually how it would be used though, it would be
number.ifPresent { print($0) }
Also, Iâm not sure the number of characters you save is necessarily the point here, isnât it more about clarity? And avoiding having to optionally bind ânumberâ just in order to use it could be considered clearer.
Another name to consider could be ifSome
, e.g.
value.ifSome(doSomething)
The idea is that this matches the .some(_)
case of Optional<Value>
.
Iâd like to see a solution here that handles (or at least considers the future direction of) the case of multiple optional variables. Currently available solutions such as flatMap
and if let
really start to show their inadequacy when you have to write:
if let foo = foo, let bar = bar, let baz = baz { doSomething(with: foo, bar, baz)
It would be much nicer to write something like (foo, bar, baz).ifAll(doSomething(with:))
.
You're touching here on a valuable (yet entirely different) concept of zipping values together.
Similar to Collection
's zip(_:, _:)
, you could introduce a zip operation over Optionals:
zip<A, B>(_ lhs: A?, _ rhs: B?) -> (A, B)? {
guard let left = lhs, let right = rhs else { return nil }
return (left, right)
}
With this in place you can now define other zip
s that take 3, 4, 5 etc. arguments.