Optional `ExpressibleByWrappedValue` superpowers

Putting aside the type checker costs, and only thinking about the inherent value when trying to write maintainable software, I can say that I'm conflicted about this concept of implicit conversion. The ancient reptilian part of my brain suggests that this is not a good idea: the possibility of implicitly converting from one type to another, that is, in a place where I need type A I can pass type B, can be the source of all sorts of sneaky bugs.

But in my personal experience I can say that I've found several cases where this wouldn't have been a problem at all, and in fact it would have helped with maintainable code.

Here's a couple of examples.


Suppose that I defined a type to express the relationship between 2 layout constraints, some like:

struct ConstraintRelation {
  case atLeast(Int)
  case exactly(Int)
  case atMost(Int)
}

(ignore for a second that this is better expressed by a struct with 2 properties, one of which an enum)

Where needed, I can write relation: .atLeast(42) or relation: .exactly(43).

Now, to make things simpler and more direct, I can leverage the ExpressibleByIntegerLiteral protocol, so instead of writing .exactly(43) I can just write 43: this is valuable, because just 43 clearly expresses that it's "exactly" 43, and the .exactly(...) part is just noise. Thus:

extension ConstraintRelation: ExpressibleByIntegerLiteral {
  init(integerLiteral value: Int) {
    self = .exactly(value)
  }
}

This is nice, I can just write relation: 43. But in some cases, instead of having an hardcoded literal, I have a constant defined somewhere, let myRelationValue: 43... well, I cannot use the simplified version now, like relation: myRelationValue, and I'm forced to "wrap" it again with relation: .exactly(myRelationValue).

This would be solved by an ExpressibleByWrappedValue that yields implicit conversion, for example:

protocol ExpressibleByWrappedValue {
  associatedtype Wrapped
  init(value: Wrapped)
}

Note that the protocol wouldn't probably be enough in this case, because a better definition for ConstraintRelation would be Double value instead of Int, and a conformance to both ExpressibleByIntegerLiteral and ExpressibleByFloatLiteral, so the wrapped value would have to be "both" Int and Double.

Also note that this would be solved with type unions for generic constraints.


At a domain boundary we often convert things into other things, because the same underlying concept is modeled differently in different domains.

For example suppose that, in a certain domain Foo, a user action is modeled via the following enum:

// domain Foo

enum UserAction {
  case didSucceed
  case didFail
  case didCancel
}

These are the cases that the domain is interested in, and the model focuses on those.

In another domain Bar, we care about different things for user action:

enum UserAction {
  case didEnter
  case didComplete
  case didCancel
}

When the even crosses domains, we would need to do the conversion manually:

// `userAction:` requires `Bar.UserAction`
userAction: {
  switch someFooUserAction {
  case .didSucceed, .didFail:
    return .didComplete

  case .didCancel
    return .didCancel
  }
}()

We could of course extend Bar.UserAction with a initializer that takes a Foo.UserAction (and this is what we actually do), but in every place where the conversion takes place, we would need to call it:

// `userAction:` requires `Bar.UserAction`
userAction: .init(someFooUserAction)

If ExpressibleByWrappedValue was possible, we could directly use

// `userAction:` requires `Bar.UserAction`
userAction: someFooUserAction

If this pattern repeats often for the same conversion, the savings become substantial. Also, note the .init(...) part doesn't really matter, it's just "plumbing" to support the conversion.