A dedicated function for evaluating Void returning closures when Optional instance is not nil

I want to agree unconditionally - especially because, as you note, the safety proof required for the first form is much higher as a result of it being a much broader and more powerful and thus potentially more edge-casey approach. Therefore it'll be more work to pitch. However, you then mention that Kotlin already does this - I know of Kotlin but am not that familiar with it; can you comment on how Kotlin does this and how well it's worked there?

The first approach implies a compiler mechanism with much broader implications, applications, and appeal (e.g. it probably ties in naturally if not as a prerequisite to constrained types and many other powerful language enhancements). If it does end up being implemented for those reasons, having a redundant if let optional { syntax - while not the end of the world - is, well, redundant.

Bikeshedding: I'm not sure I agree that the let number reads better than number != nil (though I'd get used to either form in practice, I'm sure). It's just that in plain English "if let number" is not a sentence, and it leaves the reader hanging with the natural question "if let number what?".

can you comment on how Kotlin does this and how well it's worked there?

I'll leave this topic for someone else. I've used Kotlin, but I cannot speak to the community sentiment or how the feature is implemented.

It's just that in plain English "if let number" is not a sentence

I'd argue that the intent is not in this case to be conversational but rather extremely concise and still very exacting. Use the same natural language trick on the thing we are trying to shorten and it performs no better: if let number = number: "If let number equal number" means nothing to me, and certainly does not imply that we are only letting "number equal number" if "number" is not nil. The degree to which we are increasing cognitive load is far less than that required to learn the if let pattern in the first place.

I want to stress that my second point is specifically that if we introduce

if let number { ... // number is not optional }

as a companion to the existing

if number != nil { ... // number _is_ still optional }

Then we don't break source compatibility, but more importantly the language remains equally expressive -- if I want to nil-check without changing the type of the thing named "number" I can still do that in the most obvious way possible.

Verbosity is only one motivation for this suggestion.
A much stronger one, in my opinion, is providing a declarative way of plucking out Optional's honest value. And from your solution it seems we agree that the if let dance is imperative overhead.

However, you actually touched on what I dislike about your solution:

This magical transformation unwraps the value implicitly, and assumes it's a let.
Why not var? What if I only want to check that number has an honest value, but without needing to unwrap it?

This secret unwrapping, as you called it, hides intention and holds back on clarity. In other words I'd say it's no longer declarative.

That's why the analogy to Array's forEach(_:) makes a lot of sense to me - To allow to do something with each value in the container, just with a different type of container. And since map(_:) is unclear in callsite, I'm suggesting forValue(_:).

1 Like

I don't understand why this topic is still considered.

You have also Result ( specialized Either ) monad. I would like to do the same ( print a value or an error ).

Should we consider to add forEach or let and forEachError and letError functions with the same functionality to this type?

extension Result {
  func `let`(_ success: (Success) -> Void) {
    map(success)
  }
  func letError(_ failure: (Failure) -> Void) {
    mapError(failure)
  }
}

More bikeshedding: When I write Swift, nil is seldom what I'm interested in. What I need to know is if the variable has a value, and if I can bind it. Expressing that in a negated clause about nil, which I wasn't interested in to begin with, doesn't express the intent of the program.

4 Likes

I think your point about verbosity only being one factor is definitely key. I realized only after my latest post in this thread that there’s a totally separate pitch right now that is proposing adding if let something as shorthand for if let something = something with largely different motivations ([Syntax] Default name for optional binding self and other properties).

I guess where I land in the end is I acknowledge that they address a different set of goals that merely overlap and I happen to be more interested in seeing the if let shorthand but (1) I can’t think of an argument against having the method you are looking for, (2) there’s precedent with forEach, and (3) I’d probably use it on occasion (as has been the case with forEach).

1 Like

I'm just describing a mental model, for illustrative purposes. Of course, if the value is unused, I wouldn't expect any actual unwrapping.

I hate the Kotlin model because it force you to structure your code to workaround limitations of the compiler. There is many places where the compiler is not smart enough or to much conservative and force introduction of intermediary variables or usage of force unwrap to make it accept the code.

The situation improved slightly with Kotlin 1.3 and method annotation, but it remains fragil IMHO.

1 Like

I don’t like forValue, ifLet, or IfSome.

I’m in favor of:

if value? { print(value) }

and

if let value { print(value) }

and

value.unwrap { ... }

and

var foo?
var bar?
unwrapping foo, bar { ... }

value.unwrap has the problem that you can only unwrap a single value for the closure, but I like the clarity.

unwrapping foo, bar is clear and can work with multiple values, but requires introducing a new keyword for this single purpose. The other thing is that it wouldn’t lend itself to being combined with other conditions in an if statement, which may lead to be tiny of ifs inside of unwrapping which leads to ugliness.

try is terrible because it overloads the existing meaning of try.

But of everything I’ve seen, I think if number? might be best. The ? lets everyone know we’re talking optionals, the syntax is short, we can easily handle multiple unwraps, and you can combine with other conditions in your if statement. The one thing I don’t like is that it doesn’t scream that the thing is being unwrapped, but I think I could live with that one.

What reason does anyone have against this one?

2 Likes

+1 for the increased readability and brevity.

I just don't get why this tiny cosmetic addition would be worth standardizing and dragging through the evolution process. Manual implementation can be 5 lines short and allows everybody to name it however they like:

extension Optional {
    func forSome(_ action: (Wrapped) -> Void) {
        if case .some(let wrapped) = self { action(wrapped) }
    }
}
  1. I wouldn't have to wonder if it's implemented in the project I just opened or not
  2. I wouldn't have to wonder how is it called if it's implemented
  3. I wouldn't have to wonder in what file to put it, and what name the project maintainer would like most if I have to add it
  4. I would be able to share code snippets online
  5. I wouldn't have to write documentation/answer questions to describe what is it for (the names for this feature aren't great at being self documenting)
2 Likes

All that applies to every project agnostic code. Yet there's a nice place for non-standard project agnostic code and it's huge: open source.

The question is whether this 5 line implementation is worth the resources (attention, cognitive effort, mostly time) that go into the evolution process, relative to other potential improvements of Swift.

In contrast to Result, forSome is not a construct that I see many (any!) implementations of, and even if there were, it wouldn't equal the same need for standardization.

1 Like

I think it would be as useful as .toggle on Bool, and I'd use it about as often: sometimes. We got toggle, but this one is an unresolvable discussion every time.

2 Likes

That criteria if applied to the existing standard library would eliminate a lot of it.

In my opinion it's important that writing Swift be an easy & empowering process, and similarly that reading it be quick, easy, and clear. I use Python as an example & reference here, and this specific aspect of Python is one of the main reasons it is so popular as a language, particularly to beginners.

I assert, thusly, that the Swift standard libraries should endeavour to include all necessary functionality to that end and within its purview*, and that precluding anything that 'can' be implemented separately is a non-goal.

* = By which I mean, please don't strawman with "so why doesn't it include a HTTP server, or machine learning code, or a potato baker" - the standard library in question is the language 'glue'; the core and broad functionality that underlies all code. The functionality proposed & discussed in this thread is unequivocally suited for the standard library.

2 Likes

I agree in principle as a matter of pragmatism, but the exploitable problem with this is that it permits filibustering. I've seen it bring down many teams and projects.

As a matter of governance, I'd prefer - and I think history demonstrates it's more successful - a benevolent dictator or equivalent; a Guido, or a Linus, or similar. Chris Lattner used to exert a strong influence even if not to quite this degree, years ago, but I've been out of touch and all I can see today is that it appears he doesn't anymore, nor has anyone stepped into his stead?

I know it's asking a lot, but could you dig up some examples for us to all learn from? Or even better, any discussions amongst the Kotlin language team detailing the implementation challenges?

Yes, I think so! There aren't any footguns in the implementation, it's easy to understand, doesn't shake up the rest of the language unlike some alternatives in this thread, and it could potentially be used in any kind of project, from raytracing renderer to a calculator. The fact that it's 5 lines long is a good thing. Code is evil, and less of it there is, the better.

My preference would be ifPresent { ... } which would go nicely together with a possible counterpart ifAbsent { ... }

It sounds like there's some convergence - though far from consensus yet - towards if number? {. Which I see as syntactically a terser variant of if number != nil {. So I'm interested in trying to determine which of those two is the superior…

In favour of if number != nil {:

  • It's existing valid syntax; the proposal is to enhance its function in a purely additive function (i.e. convert 'number' to an implicitly unwrapped or at least implicitly unwrappable version of itself within the if's body).
  • It has a natural and elegant counterpart: if number == nil {.
    • What would if number? { have? if !number? {?

In favour of if number? {:

  • It's (barely) shorter.
  • Iff it's implemented as syntactic sugar for if let number = number {, then it's more logically related to that family of functionality via common presence of the ? character.
    • However, if that's the intended story about it, then there are minor conceptual complications around whether it's if let … vs if var ….
    • On the other hand, if we want it to be compossible with other conditionals in a single statement, then it's more like just syntactic sugar for if number != nil {

Less clearly a pro or con (IMO) is that if number? {:

  • Is [arguably] more constrained in its possible function & interpretation thereof.
    • One could consider this a positive because it's less likely to mislead users about the language's current capability; if number != nil { triggering unwrapping implicitly might lead people to assume other intelligence from the compiler with other types than Optional or with other syntax entirely (e.g. if number >= 0 { so now I can implicitly cast to UInt, right?).
    • On the other hand, one could consider it a negative because in the long term, if one believes that the compiler will eventually accumulate such smarts, having the distinct syntax will be redundant.

IMO composition with other conditionals per normal if capabilities is important. If that's taken as a given, then if number != nil { seems superior. But the differences are minor and, importantly, neither approach precludes later introduction of / replacement by the other. So I'd be fine with whomever actually implements it in the toolchain getting to chose between these two, out of respect for their effort.

I think either of these two approaches are clearly superior to any variation of methods on Optional if only because these ways multiple Optionals can be checked together conveniently and concisely (among other benefits). These two approaches can also conceivably - certainly for number != nil, at least - be easily [re]used with the ternary conditional operator, or in any other place where a simple expression is expected. They minimise surface area of the language.

This method should be in the standard library, because forEach is in the standard library, and this method expresses the same semantics, but for optionals. This alone should be a sufficiently strong argument for its inclusion, and the discussion could just be about a good naming for it.

That being said, let me quickly iterate on three reasons why a method like forEach (and its equivalent for optionals) makes sense.

1
For those of us who try to carefully represent and express side effects, and separate them from pure code, methods like forEach make code more readable and understandable because they clearly signal that some side effect is going on there. This means of course that map must only be used for pure code: sneaking side effects in would be an improper use of the method, and an incorrect understanding of its semantics. forEach is like using bold to emphasize a word in a text: signals that something sketchy is going on.

2
A return statement in the closure passed to forEach doesn’t affect the external scope, thus not causing possibly unwanted exits from the whole function. By clearly isolating its scope, it makes the code, again, more understandable. BTW Kotlin handles this terribly, and in general Kotlin is not a good reference for this whole topic, its let construct is bad, overused, makes control flow hard to understand, and collapses optional map, flatMap and the possible ifSome.

3
Being just a method, and not a language construct that serves as syntactic sugar, it’s intrinsically more resilient to language changes and the language evolution in general. With the exception of the guard statement, which I consider extremely useful in many situations and actually helps making the code more understandable, using functional constructs like map and flatMap can be a more resilient strategy in the long run.

3 Likes
Terms of Service

Privacy Policy

Cookie Policy