Presencable Protocol


(Dmytro Pylypenko) #1

Hello Swift community,

yesterday I made a proposal about the implementation of specific protocols and support property presence for potential types.

Here is the pull request with the provided information:

And the direct link to the proposal document:

What do you think?


(Adrian Zubarev) #2

To begin with, how does this proposal has already an SE number if it's still in its pitch state? Please remove the SE number from your proposal. I changed the title and moved this thread into #evolution:pitches, which is the right place for it.


My personal 2 cents about the idea:

I'm not a native English speaker and it's the first time I ever encountered the Presencable and Emptiable words, which read and sound very strange to me. In that sense I don't think they are intuitive for anyone to discover them quickly.


I also don't see any value in the Presencable protocol as its behavior is super strange on collections.

extension Presencable {
  var presence: Self? {
    return isEmpty ? nil : self
  }
}

The motivation in the proposal is not clear, at least it's not clear to me.

// `[Array]` is not a well formed type
func retrieve() -> [Array] { ... }

The first example:

let array = retrieve()
guard !array.isEmpty else { return }

At this stage you know that the collection is not empty and it's already non-optional. I don't understand how the proposed solution is better then the above guard where you'd wrap, then again unwrap the collection into and from an optional just because it's empty or not.

guard let array = retrieve().presence else { return }

It's not clear what the second example means and why the presence wrapper is any better than the result expression.

func action() /* missing a return type */ {
  ...
  return array.isEmpty : nil : array
}

(Dmytro Pylypenko) #3

Descriptions are updated


Regarding to your questions,
names under negotiation, if you have some propositions – just propose :)

The idea to use Presencable protocoled types values only if they are not empty.
In this case, use-case where you process around a value with the strict understanding that it's not empty.
For example: array.presence?.first! is totally correct, and somehow we can rid of IUO operator, and just write array.presence?.first.


#4

I like the property isEmpty on stuff, but what is the use case for Emptiable protocol? I can think of many functions that take parameters that must be non-emptiable (returning a first element of a Collection for example) but I cannot think of any function that uses Emptiable as a parameter. Could you give me some?

I don't like presence. It would be very confusing when you're dealing with Optionals ( I can already see the confusion when people will write let x: [Int]? = []; let y = x.presence and y isn't nil ).

let x: [Int]? = []; let z = x.map({ $0.presence }) will give you a double-optional which are hard to think about (people will be trying to remember was it .some(.none) for empty array, or .none?)

let x: [Int]? = []; let v = x?.presence will lose information.

Or maybe Optionals won't be Presencable even though they certainly can be empty or not?

I also don't see how guard let array = array.presence else { ... } is better than guard !array.isEmpty else { ... }. It certainly is less obvious what it does


(Adrian Zubarev) #5

In that case you should use flatMap not map.


#6

unless I messed up the code in my head flatMap would be identical to x?.presence and would lose information about if the array is missing or just empty. If that information wasn't useful, then the array shouldn't be Optional in the first place.


(Dmytro Pylypenko) #7

If the array is just missing, there is no need to use presence.
But in the case, when you try to retrieve truly not empty, you can use it.

let a = Optional<[Int]>([1, 2])
let b = Optional<[Int]>([])
let c = Optional<[Int]>.none

if let value = a?.presence {
  print(value) // [1, 2]
}

b?.presence // nil
c?.presence // nil

(Svein Halvor Halvorsen) #8

Or, you know, just array.first :upside_down_face:


(Dmytro Pylypenko) #9

array.first – is optional, when in presence case it obviosuly exist.

FYI,

and somehow we can rid of IUO operator, and just write array.presence?.first
which will be not optional in this case.


(Svein Halvor Halvorsen) #10

Sure, but the returned type array.presence?.first will also be optional. In fact, won't it be a doubly wrapped optional?


(Dmytro Pylypenko) #11

Sure, but the returned type array.presence?.first will also be optional.

Currently yes, I am about opportunities to have in this way not optional first, last, when we are sure through presence that array is not empty.

In fact, won't it be a doubly wrapped optional?

No


(Svein Halvor Halvorsen) #12

This isn't possible. You could update your implementation of presence to return a NonEmptyCollection or some other new type that has first, last, etc that return non-optionals. However, since presence returns an optional, optional chaining would propagate the optional and the result would be the same. At some point or other you would have to guard for nil, and I fail to see any provided use case where your suggestion offers simpler syntax or more readable code.


(Svein Halvor Halvorsen) #13

Right. It's flattened automatically.


(Dmytro Pylypenko) #14

This isn't possible. You could update your implementation of presence to return a NonEmptyCollection or some other new type that has first , last , etc that return non-optionals. However, since presence returns an optional, optional chaining would propagate the optional and the result would be the same.

I wouldn't say impossible, it just doesn't fit in current flow.


(Svein Halvor Halvorsen) #15

Yes, it is impossible. By logic.


(Adrian Zubarev) #16

Not quite correct. You could have something like this:

extension Collection {
  var nonEmpty: NonEmpty<Self>? {
    if isEmpty { return nil }
    return NonEmpty(self[startIndex], self[index(after: startIndex)...])
  }
}

(Dmytro Pylypenko) #17

Impossible doubt word at all, especially in our field.

Dirty hack:

extension Collection {
    var first: Element {
        return first!
    }
}
...

let x = a!.presence!.first // 1, not Optinal(1)

But I think you follow the idea.


(Dmytro Pylypenko) #18

@sveinhal was referring to this idea at:

You could update your implementation of presence to return a NonEmptyCollection or some other new type that has first , last , etc that return non-optionals.


(Svein Halvor Halvorsen) #19

Exactly. And that returns an optional NonEmpty. And therefore the .first called on it would also be optional when chaining.

Sure, and here you are force unwrapping.

This is logically impossible:

let array = maybeEmpty()
let first = array.nonEmpty.first`

… without either:

  • making first be a kind of optional
  • force unwrapping somewhere
  • throwing
  • trapping at runtime

The function nonEmpty above cannot exist.


(Max Desiatov) #20

Totally agree here, in languages without dependent types (and Swift is one of them) it's not possible to guarantee at compile time that an arbitrary collection is not empty in a certain expression.

In languages with dependent types (Idris is a good example) you could encode collection length in its type, here's how concatenation of a vector looks with dependent types:

(++) : Vect n a -> Vect m a -> Vect (n + m) a
(++) Nil       ys = ys
(++) (x :: xs) ys = x :: xs ++ ys

Until Swift generics at least support values as their type parameters (like Int, e.g. Array<Int, 5> for an array of 5 elements), it won't be possible to provide any guarantees about collection's length at compile time. And even then you'd need to extend the type system to support type expressions like n + m from example above for list concatenation.