Why initializers are part of a type rather than kind?

When I was trying to make a monad in swift I noticed that the only way that stops me from doing a standard content transformation operation is an initializer.

protocol Container {
    associatedtype Content
    var content: Content { get set }
    init(content: Content)
}
extension Optional: Container ...
extension Identity: Container ...

func map <T: Container, NewContent, R: Container>
(_ monadInstance: T, using transformer: (T.Content) -> NewContent)
-> R where R.Content == NewContent {
    return T.self.init(content: transformer(monadInstance.content))
}
_ = map(Optional.some(0), using: {num in String(num)})

This gives the error because, in this context, type T which is Optional is referenced as a specialized type, but not as a kind.

To rephrase, since Optional is a kind (a type of types) why constructors are tied to a typing context of a regular type (Optional<...> in this case)?

let val = "str"
Optional.init(val) //works
Optional<Int>.init(val) //doesnt

This looks like a troublesome inconsistency that has to be fixed.

In the context of an existential typing, to say more, it is more apparent as an issue:

extension Container {
    func map <NewContent>
    (using transformer: (Self.Content) -> NewContent)
    -> some Container {
        return Self.self.init(content: transformer(self.content))
       //I need to get Self as a kind, regardless of how its actualy 
       //parameterized
    }
}

Again, how to get a kind of a type?

1 Like

First and foremost, no, Swift doesn't support higher-kinded types. There are a few pitches here and there, and you can probably search Higher kinded type or Generic associated type in the forums. Most of them are a few years old, so if you have something to add, it'd be better to create a new thread that references the old ones instead of bumping it.

Though I'm not sure if you need it given your signature and example usage. You seem to just want to convert between any two Container, not necessarily of the same kind. In which case you can already do:

func map<T: Container, R: Container>(_ source: T, to _: R.Type = R.self, using transform: (T.Content) -> R.Content) -> R {
    R(content: transform(source.content))
}
_ = map("str" as Optional, to: Identity.self, using: { $0 })

This should be the same map function, I just adjust the API a little bit. It could also be that what you want is actually a generic associated type, but you are nonplussed as to how to express in a more swifty form:

protocol Mappable {
  associatedtype Mapped<T> // Not supported.
  func map<T>() -> Mapped<T>
}
2 Likes

I want these two:
X<A> -> ((A) -> B) -> X<B>
X<A> -> ((X) -> G) -> G<A>

∀A∀B X<A> -> ((A) -> B) -> X<B>

If X is generic (like Optional), it is relatively easy, and Optional.map is essentially that.

If X is a protocol (like Container), there really isn't any way to achieve that right now. It would work with Generic Associated Type, or essentially any feature concerning higher-kinded types (HKT).


∀A X<A> -> (∀B (X<B>) -> G<B>) -> G<A>

We'll need higher-ranked types (HRT), which would be far more complex and rare, so I don't think it'll be supported before HKT.


Are HKT and HRT missing from Swift? Well yes. There are some old posts on this forum that tried to implement monads and functors, which IIRC failed in the end, so there's definitely a gap there.

3 Likes

Form what I have seen I can conclude that swift already kinda has HKT; it is apparent in this example:

Init maps Optional to Optional<String> to Optional<String>.some("str")
The problem is that these "metatypes" relate always only to some specialized kind and thus you cannot reference kind' init, but rather have to use it being tied to a generic context of a particular type. You cant do this Optional.self, only this Optional<Type>.self. This is the only limit I see.

Here is another example showing limitations of inits I have found.

protocol Initializible: Constructible {
    associatedtype Value
    init(with value: Value)
}
protocol Constructible {
    init?<T>(from value: T)
}
extension Initializible {
    init?<T>(value: T) {
        self = Self.init(with: value)
    }
}

It shows Cannot convert value of type 'T' to expected argument type 'Self.Value', which is obviously erroneous since Value and T have identical bounds.

It actually doesn't. It only infers Optional to be Optional<String>, which is still the simplest kind. There is no point in the program that has an unspecified Optional, which is needed for HKT.

That is what expected of the current type system. The init(value:) default implementation only knows init(with:) of type (Value) -> Self, and Value is bounded to only single type for each Self.

1 Like

Uhm, I certainly disagree with you. This feature is present in scala under the name Type Kinds and it was shown that there is a ton of usage for it. And yes, in scala they are called HKT.

That's not true.

protocol MaybeInit {
    init?<T>(from value: T)
}
struct Box<Value> { var value: Value }
extension Box: MaybeInit {
     init?<T>(from value: T) {
          self = Self.init(value: value)
     }
}

Here T and Value have the same constraints, and as such, it should be possible to construct Box with a value of T.

I think @Lantua means here that at no point in time is there an Optional without a Wrapped type (it's just it's inferred from type context). Not that there's no point as in no purpose or benefit to HKTs.

6 Likes

Either way, there is a value in it. You can imagine the hypothetical scenario involving some container whichs content is irrelevant for certain kinds of usages.

struct Box<T> {
    var value: T
    func affectValueSomehow() {...}
}
func affect(box: Box) { box.affectValueSomehow() }

The more I think about it, the more I convinced that swift must have type constrained by kind. Don't you think, Ben, that the following would be useful?

func map <T: Box, NewValue, Output: T> 
//T is constrained to be of the same kind as T
(value: T, using transformer: (T.T) -> NewValue) 
-> Output where Output.T == NewValue {...}

Preamble: I'm not as experienced in Swift as I'd wish, so my assumptions may be totally wrong.

It's true that your Box struct has a generic type associated with it and as such it can be of any type, without constraints. On the other hand, if a Box instance already exists, then its Value type is determined and can't mutate.

extension Box {
    mutating func setValue<T>(_ value: T) {
        self.value = value
    }
}

In this case the Box instance on which you're calling the .setValue(_:) method has a determined Value type, so you cannot assign a generic T to its value property. It would only work if T was exactly Value, i.e.

extension Box {
    mutating func setValue(_ value: Value) {
        self.value = value
    }
}

You're example is a bit different since you're using an initializer. The purpose of that initializer is to create the instance and at the same time to fix the Value type. However there are multiple ways to initialize an instance:

let a = Box(from: 3)          // a: Box<Int>
let b = Box<Double>(from: 3)  // b: Box<Double>

There are scenarios in which the Value type is fixed even before the initialization of the instance takes place. So the behavior is the same of the instance method's one: you cannot assign a completely generic T type to a particular and determined type. You need to define it as init?(from value: Value).

Instance methods and constructors are different things. Constructors of generic types are restricted only by bounds of type variables, not concrete types. Methods are always invoked on instances of specialized types.

In the case of the kind, there is not.

To answer your original question as to why initializer is not attached to the kind, it is because there is no kind in Swift. Every instance where you thought is a kind is actually a type that omits generic parameter (my original wording causes some confusion, sorry about that). That is precisely why this doesn't work:

let val = "str"
Optional.init(val) //works
Optional<Int>.init(val) //doesnt

The first one uses Optional<String>.init, while the second uses Optional<Int>.init. The second init fails because there is no initializer in Optional<Int> that takes a single, unlabelled String.

Since Swift only deals with type, when you extend a generic, it extends each type of that kind rather than the kind itself. That is why when you write

extension Box {
  init<T>(from: T) { ... }
}

you are adding init(from:) to each Box<Value> and not Box itself. So when you write Self inside the extension, the compiler assumes that Value is already bound, independently of T. So the type of Value are not necesssarily the same as T, that's why the compiler fails to find the matching initializer.

5 Likes
Terms of Service

Privacy Policy

Cookie Policy