Add `filter` and `ifSome(then:)` to Optional

I like the proposed Optional.filter but not the Optional.ifSome(then:)

2 Likes

It’s true that map is borrowed from functional programming, but Swift has no concept of ‘pure’ functions and has never made such a requirement for map. The Swift Programming Language itself has (or at least, one of its editions once had) an example of map(print), so I think it’s fair to say that such usage is ‘idiomatic’ Swift.

1 Like

I imagine any such use of map is accidental and probably worth correcting. If it were the idiomatic way of executing (A) -> Void, we wouldn’t also have forEach.

5 Likes

I’d give it an not-particularly-passionate yes. The ergonomics are nice; the need is not dire.

I’m not wild about the name filter for the first. I see the parallels between optionals and sequences, but it’s a limited parallel. The name is just as likely to miss type errors as it is to provide useful understanding.

See previous discussion of why Optional doesn’t conform to Sequence or Collection, and the downsides of reusing names between the two:



[Edit: wow, those intra-forum links are HUGE.]

About forEach: Apparently, early versions of Swift didn’t have it. Why it was added is unclear to me. That said, back then, you did have to write _ = even when the return value was Void, and I wonder if that was part of the issue. Chris Eidhof, both on this list and in Advanced Swift, has argued against using forEach. So, I wouldn’t say that there’s general agreement as to the role of forEach in Swift. But on the flip side, I guess using map in the same role is rather controversial.

1 Like

I don’t think that’s a particularly useful comparison to make. Optional doesn’t have map because it’s a Collection, Optional and Collection are both functors. Similarly, Optional doesn’t have flatMap because it’s a Collection, Optional and Collection are both monads. I have body hair, not because I’m secrely a mouse, but because both mice and I are mammals.

This is the case for functions that return Void, yes, but there are functions that return results you might not care about, and might want to discard. As I note, Set.remove(_:) is an example of this. It will either remove the element from the set (returning the element), or it will do nothing (returning nil). If your interest is just to ensure the Set no longer contains the element you pass into remove(_:), then you have no use for the result, so you’ll want to discard it.

7 Likes

I have no intention of make parallels between Optional and Collection, filter was strawman syntax. It could be called discard(if:), or something else. The parallel to collection was unintentional

1 Like

You’re certainly right about the reasons why Optional has map and flatMap.

That said, if mice were defined solely as mammals with body hair, then you’d be a mouse. By the same token, Optional meets the semantic requirements to be a Collection; it’s just not a very useful conformance, and a potentially confusing one.

If we’re being pedantic, all mammals have body hair :)

Let me introduce you to the naked mole-rat:

1 Like

They have little hair

Just because they have “a little” hair doesn’t mean they don’t have any. A mammal, by definition, must possess hair:

Mammals are the vertebrates within the class Mammalia (/məˈmeɪliə/ from Latin mamma “breast”), a clade of endothermic amniotes distinguished from reptiles (including birds) by the possession of a neocortex (a region of the brain), hair, three middle ear bones, and mammary glands. Females of all mammal species nurse their young with milk, secreted from the mammary glands.

-Wikipedia’s article on Mammals

1 Like

If you had as much hair as a naked mole-rat, you wouldn’t be saying, “I have hair” :)

The definition of a mammal does not rest on any particular physical attribute; taxonomy hasn’t worked that way for a long while. Take it from someone who created a genetically modified mouse strain.

1 Like

So, how would you classify a mammal?

(As a sidenote, one other thing that Discourse doesn’t seem to do is allow for private discussions “off-list” instead of polluting threads with tangents. Is there a better way to do this?)

We should at least try to avoid tangents over definitions of words, which are inherently flexible. Your previous classification of mammal would suggest hair removal surgery is all it takes to switch branch in the family tree. We could nitpick things like this all day, but it’s a wasted effort as long as we all recognise what’s being discussed.

As a general rule, if a tangent is worth discussing, I’d say it’s worth making a new thread for.

3 Likes

Not only confusing, but dangerous with the existence of implicit optional promotion.

func foo<C : Collection>(xs: C) -> Int {
  return xs.count
}

// This code would compile
let WAT = foo(42)
5 Likes

forEach is a nice method to be used to terminate the call chains like xs.filter { ... }.map { ... }. The alternative being to then break the flow, return all the way of the beginning of the chain and wrap it into a for ... in ... loop this way or the other.

Also let’s not forget that map is not free, as it always allocates an array to hold the result.

1 Like

FWIW:

I said it in the past somewhere, and I still believe that porting some Collection methods to Optional would be nice. However I disagree with the ifSome(then:) name. We already have a name for such operation and it’s forEach(_:).

1 Like

We need to stop the forced parallels between Optional and Collection, just because Optional has map and flatMap, doesn’t mean we need to crowbar in every API that seems to do something similar. I’ll defer to my previous statement:

Following this logic, any type that is a functor (implements map) and that is also a monad (is a functor that also implements flatMap) should follow suite. Promise is one such type. Let’s have a Promise.forEach. Compare the syntax:

let p: Promise<File> = downloadFile("www.something.com/file")
p.then { print($0 } // makes sense!

p.forEach { print($0) } // wut

We could make some forced connection between promises and collections, saying that a Promise is a collection of 0 or 1 values: but it just doesn’t serve us any good. We chose then(_:) because it makes more sense than forEach(_:), even if under some contrived conformance to collection, they would do the same thing.

Naming ifNone(then:) opens up the opportunity for also having ifNil(then:). What would the counterpart to forEach(_:) be, in that case?

1 Like

I look at it as a matter of familiarity and recognizability, if you will, of the API. It is not about the Collection-ness of Optionals, but about this specific method, that unwraps the abstraction and applies a closure to every single value that happens to be inside. Promise fits…

I might be brain-damaged beyond repair, but it’s only Result.forEach that seems a little unnatural to me. 🤷‍♂️

Haha fret not, I don’t think you’re brain damaged.

Yes, I did concede that promise “fits” technically speaking:

But I think our APIs should center around what’s intuitively true, over that’s technically true. This is a valid way of looking at it, and I don’t contest that. The issue is that it’s not an obvious observation to make, and would certainly not be obvious to someone learning how to work with optional or promises in the first place. Even for those who do know about this similarity, like you and I, I’m sure that we can agree that getPromise().then(doSomething) is more intuitive.

I’d further like to note, that sometimes you have to work with a collection of promises. Some poorly-designed APIs only work with single cases per request, requiring multiple requests. It’s not a good thing, but it’s a fact of life we need to put up with sometimes. The distinction between the forEach of the collection, and the then of the promise is a useful distinction to make: promises.forEach { $0.then(doSomething) }.

If Promise.forEach isn’t unnatural, why do you find Result.forEach unnatural? Where’s the distinction?

And again, I’d like to pose the question:

Terms of Service

Privacy Policy

Cookie Policy