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

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

Are you implying that there needs to be a third method on top of map and forEach, i.e. a map that also has side effects? I don't think this is how these are used in practice, and I don't think forEach being “sketchy” is a general opinion. There isn't currently a compiler-aided pure function feature, and I don't think this distinction should be made by introducing redundant methods with different names and no enforcement.

If this method is going to be added, or a change made to the if-statement, then I think it's going to be on the grounds of teachability and removing the repetition of the variable name from the current if-let.

I don’t think you understood my post: I use forEach a lot, and to me (and again, to those that value careful side effect management) is a valuable tool, it’s not the method that’s “sketchy” in itself, it’s the fact that using it clearly signals a computation that’s not pure. A third method would be pointless and harmful, I’m saying exactly the opposite.

I don't think you understood my post: what if you want to do a map that also has side effects? forEach cannot do this. If you want to use map vs forEach to signal purity, then you would need a third “impure map” function. This just isn't what these methods mean in Swift, because there's no concept of a pure function.

You shouldn’t do a map that also has side effects (or, in other words, you shouldn’t put side effects in map), it would conflate 2 things that should be separate. I understood your post, and I’m not implying anything of the sort.

That might be your personal understanding of what map should be used for but, as there is no concept of a pure function in Swift, that's not what it means. You can freely perform side effects in a map, and you shouldn't presume that a map is free of side effects. Similarly, you can write an instance method that both returns a value and modifies the instance, e.g. remove(at:) on Array.

This claim pops up every so often, and I think it's important to state very clearly that this is incorrect for Swift; while the method shares a name with operations in other languages that don't allow side effects, the semantics and even the syntax of map in Swift supports side effects. Let me put it another way: @jawbroken says (correctly) that there's no concept of a pure function in Swift, but even if there were such a concept, Swift's map would still support side effects. Let me give you just one reason why:

Notice that map is a requirement of Sequence, not Collection. The salient here difference between Sequence and Collection is that the latter guarantees that a type to be finite and multipass. Notice that map doesn't require either guarantee. If you invoke map on a single-pass sequence, that consumes the sequence; if you invoke map on an infinite sequence, then you can only hope to do so for its side effects, since it never returns. Swift explicitly requires map for Sequence; in other words, the language guarantees such uses to be possible, even though it did not have to.

3 Likes

Nothing prevents @ExFalsoQuodlibet from only using map in a pure manner, so his point of view is quite understandable.

3 Likes

The problem is that nothing stops collaborators/users from using unpure map, or supplying unpure function (to a library) when it’s implicit in the implementation, but doesn’t really have Swift support.

I completely understand this, in fact I can provide one more reason why Swift map, while using (in my opinion, wrongly) a name that has a certain theoretical background, is a different thing: map on a Sequence returns an Array, and not the same concrete instance on which it was called.

But in my point 1 I wrote For those of us who try to carefully represent and express side effects, and separate them from pure code, because I knew that it would be applicable only to certain people, and that the idea of a pure map in Swift is really only a convention. But in my context, for the code I write, it was a valid argument for the existence and utility of forEach, and in parallel, for the inclusion of the method proposed in this pitch.

1 Like

@ExFalsoQuodlibet can use any method however he likes, but that doesn't generalize to:

There is no such demand on the part of Swift for users other than @ExFalsoQuodlibet.

1 Like

Yes, you are free to establish any convention you like for you. You are correct that your convention is a valid argument for adding a differently named method in your context, for the code you write. Perhaps it would be a useful part of a library "for those who try to carefully represent and express side effects."

But your convention explicitly tells users not to use a standard library function in a way that the standard library itself permits and even guarantees. By your own argument, then, the incompatibility of your convention with the existing design choices made in the standard library argues against such an additional method being provided in the standard library.

(I am not expressing an opinion here as to whether there might be other arguments to be made in favor of making such a change in the standard library, just that this argument isn't it.)

2 Likes