Reversible Initializer

Hello, this is my first attempt at sharing an idea about programming syntax.

I've noticed that quite often we initialized a class or a struct with a single parameter and sometime this can lead to

URLRequest(url: URL(string: string)!)

== STATE OF THE IDEA ==

the idea evolved to something more simple thanks to suyashsrijan

struct Something {     
    var string: String
     
     reversible(asSomething) init(string: String) {
         self.string = string
     }
}

and then

let something: Something = myString.asSomething

we could even say that if it default to "asClassname"

reversible init(string: String)

is shorthand for

reversible(asSomething) init(string: String)

and this would basically simply implement:

extension String {
     var asSomething: Something  {
          return Something(string: self)
     }
}

Or as an optional if needed

== Original Post ==

What if we could declare an initializer as reversible such as:

struct Something {     
    var string: String
     
     reversible init(string: String) {
         self.string = string
     }
}

So that in the end we could create an instance of Something directly from the string object using for example .. between variable.

exemple :

let myString ="this is a string"
let mySomething: Something = myString..something
print(mySomething.string) // "This is a string"

so for example

let request = URLRequest(url: URL(string: String(substring))!)

would become if all had reversible initializers

let request = substring..string..url..request

I have no idea if this is actually doable or really useful but what do you think of it?We could call it the real Yoda syntax :slight_smile:

2 Likes

Could you not do this with an extension? For example:

extension String {

  // Obviously not safe because string might
  // not be a valid URL - this should return
  // optional.
  var asURL: URL { 
    return URL(string: self)!
  }
}

let urlRequest = URLRequest(url: string.asURL)

Similarly, you can do:

extension URL {
  func toRequest() -> URLRequest {
    return URLRequest(url: self)
  }
}

let urlRequest = string.asUrl.toRequest()

Yes that is what I am doing right now but the .. instead of "as" and auto implemented when you write "reversible" cause in my example you would need 4 extensions. that can add up.

You’ll also need 4 “reversible” initialisers though (instead of 4 extensions), so it’s basically the same thing. Effectively, this is just sugar over extensions I believe?

or we could make the "reversible" keyword generate a simpler "asUrl" or "asRequest".

reversible(asSomething) init(string: String) ...

and so more simply:

let something: Something = myString.asSomething

1 Like

definitely sugar :slight_smile:

And this is great for optional chaining too:

let urlRequest = string?.asUrl?.toRequest()

Much more so than using an initializer with flatMap, which adds lot of visual noise:

let urlRequest = string.flatMap(URL.init(string:))?.toRequest()
let urlRequest = string.flatMap { URL(string: $0 }?.toRequest()

I wouldn't say no to a syntax that makes this more straightforward.

I'm not sure injecting members (string.asURL) into another type from the declaration of an initializer is wise though.

Yes I also though that maybe it wasn't wise to inject from the URL class into the String class. But at the same time it would never add a heavy implementation.

Another approach could be to have something more general: a chaining operator to inject the previous result of a chain into a function call:

1.sin($).cos($).tan($)
// instead of
tan(cos(sin(1)))

That'd work nicely with optional chaining, and it could work with initializers too:

let urlRequest = string?.URL(string: $)?.URLRequest(url: $)

What determines whether this is a method call taking self implicitly or not is the absence or presence of $ within the parameters.

6 Likes

It is easy to scope the visibility of the 'reverse' function using an extension. How are you planning to handle this with reversible initializers ?

  internal reversible(asSomething) public init(string: String) {
     self.string = string
  }

Why would you want to make the reversible internal but the init public? Could you give an example? If the init is public, why or when the reverse init would need to be something else? (just asking I have not though much about it)

I like it, but It seems like a concept on its own.

let urlRequest = string?.URL(string: $)?.URLRequest(url: $)

vs

let urlRequest = string.asUrl.asRequest

I, too, often find it cumbersome to combine instance methods/properties and free functions/constructors. It makes a simple chain either read from the inside out, or worse: mixing left-to-right and inside-out.

However, I prefer fixing the issue with an “apply operator” (eg |>) that “pipes” the left hand side into the function argument to the right.

It allows a more natural flow from left to right (or top to bottom using line breaks) when combining instance methods and functional composition.

6 Likes

I agree that keeping left to right is simpler. But a pipe operator is a thing on its own.

It doesn’t seem to add much from pipe operator.

Hmm, maybe there's some qol improvement with optionals.

They pretty much solve the same issue, non?

let urlRequest = string |> URL.init |> URLRequest.init

// vs.
let urlRequest = string?.URL(string: $)?.URLRequest(url: $)

if you talk about Michelf proposition, yes it is the same as pipe but the syntax is different.

but my proposition is to declare reversible initializer so that when you implement something that you can init with a string for example, you can declare it reversible to auto implement the reverse init string extension.

Sure. But yours is also functionally isomorphic to the pipe/application operator, except the syntax is different and it requires new language support. They all solve the same issue of chaining calls, and ensuring a consistent left-to-right order of operations.

Is optional chaining implicit here, or do we also need a ?|> operator for to make optional piping chains?

Sure. I have that in some of my projects:

infix operator |> : MultiplicationPrecedence
infix operator ?|> : MultiplicationPrecedence

func |> <T, U>(left: T, right: (T) -> U) -> U {
    return right(left)
}
func ?|> <T, U>(left: Optional<T>, right: (T) -> U) -> Optional<U> {
    return left.map(right)
}

I use it e.g like this:

private func flag(regionCode: String) -> String {
    let base: UInt32 = 127397
    return regionCode
        .uppercased()
        .unicodeScalars
        .compactMap { UnicodeScalar(base + $0.value) }
        |> String.UnicodeScalarView.init
        |> String.init
}

flag(regionCode: "no")  // returns "🇳🇴"
5 Likes