Infer the type of an object's property from the type of another object's member

Hey :wave:

I am trying to infer the type of an object's property from the type of another object's member. To give you some context, please take a look at the following valid Swift code:

Code snippet 1

final class NetworkService {
    func fetch(completion: @escaping (Result<Bool, Error>) -> Void) {
        completion(.success(true))
    }
}

struct Dependencies {
    let fetch: (@escaping (Result<Bool, Error>) -> Void) -> Void
}

final class Repository {
    private let dependencies: Dependencies

    init(dependencies: Dependencies) {
        self.dependencies = dependencies
    }
}

let repository = Repository(dependencies: .init(fetch: NetworkService().fetch))

I would like to do something that is currently not possible (not valid swift at the moment, but helps to show what I would like to do):

Code snippet 2

struct Dependencies {
    let fetch: type(of: NetworkService.fetch)
}

As a comparison, the code below is valid Swift:

Code snippet 3

func setType<T, Input, Output>(of function: (T) -> (Input) -> Output) -> (Input) -> Output {
    fatalError()
}

...

struct Dependencies {
    var fetch = setType(of: NetworkService.fetch)
}

...

let repository = Repository(dependencies: .init(fetch: NetworkService().fetch))

So I have the following doubts:

  • What is the right naming convention to refer to the approach shown in code snippet 2? Maybe type inference at type annotation level?
  • I could not find much documentation about the approach shown in code snippet 2 (maybe because I dont even know how to name it in the first place, so it is difficult to search for it :smiley:) . Could any of you reference any?
  • Considering the valid code from code snippet 3, do you think that we as developers should be able to write something like code snippet 2?
  • Is there any interest on allowing an approach as the one shown in code snippet 2?

Thank you very much :pray:

There's no way to achieve what you are trying to do in snippet 2. In particular, there's no type(of:) equivalent in type annotation. Even if there is, NetworkService.fetch is of type (NetworkService)->((Result<Bool, Error>)->())->(), which is not what you'd expect. I'd recommend using typealias.

There may be a way to achieve something similar to snippet 3, which would be using type inference instead of type annotation. You can create it from the type of NetworkService.fetch that I showed above. I wouldn't recommend it. If anything, it could make the code harder to read since setType wouldn't be exactly intuitive. Also, since Swift doesn't have variadic generic (yet), you'd need one function for each number of input argument. If you insist, though, it'd be something like:

func setType<T, Input1, Output>(of: (T) -> (Input1) -> Output) -> (Input1) -> Output {
  { _ in fatalError("Do not use functions returned by `setType`") }
}

func setType<T, Input1, Input2, Output>(of: (T) -> (Input1, Input2) -> Output) -> (Input1, Input2) -> Output {
  { _, _ in fatalError("Do not use functions returned by `setType`") }
}

...

Thanks so much for the answer!

There's no way to achieve what you are trying to do in snippet 2.

I am aware of it, I did mention that code snippet 2 is not valid swift.

I am interested on the the points I mention in the post, if you could provide an answer to them :slight_smile::

  • What is the right naming convention to refer to the approach shown in code snippet 2 ? Maybe type inference at type annotation level ?
  • I could not find much documentation about the approach shown in code snippet 2 (maybe because I dont even know how to name it in the first place, so it is difficult to search for it :smiley:) . Could any of you reference any?
  • Considering the valid code from code snippet 3 , do you think that we as developers should be able to write something like code snippet 2 ?
  • Is there any interest on allowing an approach as the one shown in code snippet 2 ?

No idea.

There's nothing similar in Swift. So I don't think there's any doc.

Yes, if you consider let x = setType(of: y) to be similar to let x: type(of: y).

There are a lot similar questions lately. Usually they are titled with full-on sentences like this one (so no name yet), but there is no one expressing a will to implement it (as far as this forum is concerned).

A list of posts that I could dig out:

There are a few more, but I couldn't find it.

Thanks so much, it helps to get some context about the ongoing discussions :pray:

I would like to elaborate a bit more. The current typealias syntax is really just a type transformation, with an input and an output:

typealias NewType1 = String
// Transformation name: NewType1
// Transformation input: Void
// Transformation output: String

Another example:

typealias NewType2<T> = Array<T>
// Transformation name: NewType2
// Transformation input: T
// Transformation output: Array<T>

So we could think about typealias as if they were functions:

func newType1() -> String { fatalError() }
typealias NewType1 = String

func newType2<T>(_: T) -> Array<T> { fatalError() }
typealias NewType2<T> = Array<T>

func newType3<T>(_: T) -> Array<T> where T: Equatable { fatalError() }
typealias NewType3<T> = Array<T> where T: Equatable

That make sense: our existing syntax for typealias does match our existing syntax for functions, at least when looking at simple type transformations.


But what happens when we look at more complex ones? What would be the equivalent typealias for the following function?

func getType<Parent, MemberType>(_: (Parent) -> MemberType) -> MemberType { fatalError() }
// The equivalent typealias name would be GetType

The usecase is the same one mentioned above: infer the type of a property from the type of an existing member. For example, if we could define the equivalent typealias for the above function, then it would be possible to do:

struct Dependencies {
    let fetch: GetType<NetworkService.fetch>
}

About the syntax for that last case, it is a bit uncertain. If extending existing syntax to support this, I am not sure the following will work in every case:

typealias GetType<(Parent) -> MemberType> = MemberType

Maybe we would need some new keyword:

typetransform GetType<(Parent) -> MemberType> = MemberType

Thoughts?

I don't have a strong opinion for that kind of type aliasing/referral.

If you think that it could become a full-fledge and useful feature you could try to turn it into an evolution. The process is documented right here, which includes pitching the idea over #evolution:pitches category to gauge the community reaction.

1 Like

There might be a way of doing this without adding syntax, as part of a solution to a larger problem. We already have this "hole" in the type inference syntax:

func a() {
    let y = 0 // works fine
// let x doesn't work, we have to write the type explicitly:
    let x: Int
    if someCondition {
        x = 1
    } else {
        x = 0
    }
}

It seems a straightforward concept that x's type could be inferred just like y's. The only restriction is that it must be possible to make the same inference on every path through the code.

I can imagine doing something similar for instance properties:

struct A {
    let x
    init() {
        x = 0
    }
    init(_ someValue: Int) {
        x = someValue
    }
}

The restriction here would be that x must be inferable to the same type on every path through every initializer.

IOW, instead of solving this specific problem, we could try expanding the reach of existing type inference.