Global Implementation of `some`

you can receive an error about some not being implemented in this circumstance(at least in 5.9) where any also doesn’t work as anticipated for example

public typealias code = (_ context:Dictionary<ContextID,Any>,_ Targets:inout [Target], _ Params:  Input, _ caller:inout some Caller) throws -> Output //throws “`some` types are only implemented for the declared type of properties and subscripts and the return type of the function”

Note:
I am using some to prevent a inout value from being convertible to anything that conforms to the Associated Protocol(inout uses the &<#value#> notation to directly reference the pointer value of the function) due to impartial type safety when relating to generic protocol types

I believe the some keyword should match where the any keyword may be placed as their duties are partially equivalent due to having similar features(ie. Providing a socket for interfaces where any only requires conformance and some also introduces static typing(if someone knows a better term, feel free) preventing object transmutation)

The some shorthand is not available in typealiases, because Swift does not have first-class generic function types.

some, when used with a function parameter, is a shorthand for a generic type.

As a workaround, if you need to preserve the generic-ness of the function, you could wrap it in a protocol:

protocol code {
  func theFunction(
    _ context: Dictionary<ContextID,Any>,
    _ Targets: inout [Target],
    _ Params: Input,
    _ caller: inout some Caller
  ) throws -> Output
}

And then instead of closures, use structs which implement the requirement:

struct Demo: code {
  func theFunction(
    _ context: Dictionary<ContextID,Any>,
    _ Targets: inout [Target],
    _ Params: Input,
    _ caller: inout some Caller
  ) throws -> Output { 
    /* Your implementation */
  }
}

You can then declare a variable of type some code or any code, and access the generic function from there:

var example: (any code)? = nil // ok, even though we never bound T

example?.theFunction(...) // is a true generic function.
3 Likes

Do you know if there is a reason why swift doesn’t have first class Type Generics?

Also, code is a typealias to a closure that itself is required by a protocol and the variable it types is designed to be easy to use and not unnecessarily clunky. The snippets provide code that isn’t implied.

My code passes the inputs of a function not requiring a full type declaration.

Also, some types do not work here either as in this code as it still causes the error in when set up in a minimal test environment.

It's only for function types -- we have generic functions, but you can't have a variable whose type is a generic function type. I don't know why not; maybe somebody else knows if there's an objection to the principle or whether it's just not implemented.

You can make a generic typealias, but there's a subtle distinction:

typealias Foo = <T: Numeric>(T) -> String // not allowed
typealias Bar<T: Numeric> = (T) -> String // okay

Foo is not allowed because the left-hand side is not generic and the right-hand side is: it's a non-generic typealias to a generic function type. That's not inherently wrong, but the right-hand side (the generic function type) cannot currently be expressed in Swift, so it is not valid.

Bar is allowed because the left-hand side is a generic typealias, and the right-hand side only depends on the parameters it introduces (T); it's not independently generic. Any time you actually use Bar, you will have to bind T to something, and the result will not actually be a generic function:

var invalid: Bar    // invalid - cannot infer type for T.
var works: Bar<Int> // okay - but this is not generic; it only accepts Ints.

struct Holder<U: Numeric> {
  var invalid: Bar  // invalid - cannot infer type for T.
  var works: Bar<U> // okay - but also not generic; it only accepts Us.
}

Now, going back to the some shorthand:

typealias Baz = (some Numeric) -> String

This is equivalent to the (invalid) Foo above - the right-hand side is generic, but the left-hand side is not. Again, it is a meaningful thing to want to express, but it's currently not supported.

1 Like

This is called higher-rank polymorphism: Higher-rank polymorphism | PLS Lab

It’s certainly implementable but difficult. I don’t know how such functions would be encoded in our runtime generics model (@John_McCall knows this better than I do but basically “reabstraction thunks” become a lot trickier). Also type inference is undecidable with rank-3 types and above, so we would need to precisely define the rules for where type annotations must be specified (https://www.sciencedirect.com/science/article/pii/089054019290020G).

Encoding higher rank types using protocols (or even subclassing) avoids both problems because there are no implicit conversions between the nominal types defined this way, and all types are spelled out so there is nothing to infer.

10 Likes

Maybe a way to split the difference is to bring back the closure literals as anonymous structs idea. Then you could declare a protocol with a generic callAsFunction requirement, and a closure literal passed in some Protocol or any Protocol context would be able to be generic according to Protocol's callAsFunction signature.

9 Likes

Swift is actually relatively well set up for higher-rank polymorphism because we support generic execution already. I don’t think reabstraction specifically would be a problem. You do need conversions that change polymorphism, though, because the natural subtyping rules permit specialization in generic signatures, e.g. from <T,U> (T, U) -> Bool to <T> (T, T) -> Bool. The biggest challenge is probably just representing generic parameters properly in the type system, both statically and at runtime.

9 Likes