Protocol function with generic type - swift

I would like to create a protocol like the following:
protocol Parser {
func parse() -> ParserOutcome<?>
}
enum ParserOutcome<Result> {
case result(Result)
case parser(Parser)
}
I want to have parsers that return either a result of a specific type, or another parser.
If I use an associated type on Parser, then I can't use Parser in the enum. If I specify a generic type on the parse() function, then I can't define it in the implementation without a generic type.
How can I achieve this?
Using generics, I could write something like this:
class Parser<Result> {
func parse() -> ParserOutcome<Result> { ... }
}
enum ParserOutcome<Result> {
case result(Result)
case parser(Parser<Result>)
}
This way, a Parser would be parameterized by the result type. parse() can return a result of the Result type, or any kind of parser that would output either a result of the Result type, or another parser parameterized by the same Result type.
With associated types however, as far as I can tell, I'll always have a Self constraint:
protocol Parser {
associatedtype Result
func parse() -> ParserOutcome<Result, Self>
}
enum ParserOutcome<Result, P: Parser where P.Result == Result> {
case result(Result)
case parser(P)
}
In this case, I can't have any type of parser that would return the same Result type anymore, it has to be the same type of parser.
I would like to obtain the same behavior with the Parser protocol as I would with a generic definition, and I would like to be able to do that within the bounds of the type system, without introducing new boxed types, just like I can with a normal generic definition.
It seems to me that defining associatedtype OutcomeParser: Parser inside the Parser protocol, then returning an enum parameterized by that type would solve the problem, but if I try to define OutcomeParser that way, I get the error:
Type may not reference itself as a requirement

I wouldn't be so quick to dismiss type erasures as "hacky" or "working around [...] the type system" – in fact I'd argue that they work with the type system in order to provide a useful layer of abstraction when working with protocols (and as already mentioned, used in the standard library itself e.g AnySequence, AnyIndex & AnyCollection).
As you said yourself, all you want to do here is have the possibility of either returning a given result from a parser, or another parser that works with the same result type. We don't care about the specific implementation of that parser, we just want to know that it has a parse() method that returns a result of the same type, or another parser with that same requirement.
A type erasure is perfect for this kind of situation, as all you need to do is take a reference to a given parser's parse() method, allowing you to abstract away the rest of the implementation details of that parser. It's important to note that you aren't losing any type safety here, you're being exactly as precise about the type of the parser as you requirement specifies.
If we look at a potential implementation of a type-erased parser, AnyParser, hopefully you'll see what I mean:
struct AnyParser<Result> : Parser {
// A reference to the underlying parser's parse() method
private let _parse : () -> ParserOutcome<Result>
// Accept any base that conforms to Parser, and has the same Result type
// as the type erasure's generic parameter
init<T:Parser where T.Result == Result>(_ base:T) {
_parse = base.parse
}
// Forward calls to parse() to the underlying parser's method
func parse() -> ParserOutcome<Result> {
return _parse()
}
}
Now in your ParserOutcome, you can simply specify that the parser case has an associated value of type AnyParser<Result> – i.e any kind of parsing implementation that can work with the given Result generic parameter.
protocol Parser {
associatedtype Result
func parse() -> ParserOutcome<Result>
}
enum ParserOutcome<Result> {
case result(Result)
case parser(AnyParser<Result>)
}
...
struct BarParser : Parser {
func parse() -> ParserOutcome<String> {
return .result("bar")
}
}
struct FooParser : Parser {
func parse() -> ParserOutcome<Int> {
let nextParser = BarParser()
// error: Cannot convert value of type 'AnyParser<Result>'
// (aka 'AnyParser<String>') to expected argument type 'AnyParser<_>'
return .parser(AnyParser(nextParser))
}
}
let f = FooParser()
let outcome = f.parse()
switch outcome {
case .result(let result):
print(result)
case .parser(let parser):
let nextOutcome = parser.parse()
}
You can see from this example that Swift is still enforcing type-safety. We're trying to wrap a BarParser instance (that works with Strings) in an AnyParser type erased wrapper that expects an Int generic parameter, resulting in a compiler error. Once FooParser is parameterised to work with Strings instead of Int, the compiler error will be resolved.
In fact, as AnyParser in this case only acts as a wrapper for a single method, another potential solution (if you really detest type erasures) is to simply use this directly as your ParserOutcome's associated value.
protocol Parser {
associatedtype Result
func parse() -> ParserOutcome<Result>
}
enum ParserOutcome<Result> {
case result(Result)
case anotherParse(() -> ParserOutcome<Result>)
}
struct BarParser : Parser {
func parse() -> ParserOutcome<String> {
return .result("bar")
}
}
struct FooParser : Parser {
func parse() -> ParserOutcome<String> {
let nextParser = BarParser()
return .anotherParse(nextParser.parse)
}
}
...
let f = FooParser()
let outcome = f.parse()
switch outcome {
case .result(let result):
print(result)
case .anotherParse(let nextParse):
let nextOutcome = nextParse()
}

Status of the features needed to make this work:
Recursive protocol constraints (SE-0157) Implemented (Swift 4.1)
Arbitrary requirements in protocols (SE-0142) Implemented (Swift 4)
Generic Type Aliases (SE-0048) Implemented (Swift 3)
Looks like this is currently not possible without introducing boxed types (the "type erasure" technique), and is something looked at for a future version of Swift, as described by the Recursive protocol constraints and Arbitrary requirements in protocols sections of the Complete Generics Manifesto (since generic protocols are not going to be supported).
When Swift supports these two features, the following should become valid:
protocol Parser {
associatedtype Result
associatedtype SubParser: Parser where SubParser.Result == Result
func parse() -> ParserOutcome<Result, SubParser>
}
enum ParserOutcome<Result, SubParser: Parser where SubParser.Result == Result> {
case result(Result)
case parser(P)
}
With generic typealiases, the subparser type could also be extracted as:
typealias SubParser<Result> = Parser where SubParser.Result == Result

I think that you want to use a generic constraint on the ParserOutcome enum.
enum ParserOutcome<Result, P: Parser where P.Result == Result> {
case result(Result)
case parser(P)
}
This way you would not be able to use ParserOutcome with anything that is not conforming to the Parser protocol. You can actually add one more constraint to make it better. Adding the constraint that the result of the Parser outcome will be the same type as the Parser's associated type.

Related

Swift. Can't use Type variable as specification to generic method

I try to make a generic method in Swift without an object of a generic type with this code:
protocol TestProtocol {}
class TestClass1: TestProtocol {}
class TestClass2: TestProtocol {}
class TestClass3: TestProtocol {}
class TestClass4: TestProtocol {}
enum TestEnum {
case one, two, three, four
var type: TestProtocol.Type {
switch self {
case .one: return TestClass1.self
case .two: return TestClass2.self
case .three: return TestClass3.self
case .four: return TestClass4.self
}
}
}
class TestMethods {
func prepareToGet(kind: TestEnum) {
getTest(as: kind.type)
}
func getTest<TestType: TestProtocol>(as: TestType.Type) {
// here I need specified type of TestProtocol to handle it
}
}
But with Type variable. But I have errors:
Cannot convert value of type 'TestProtocol.Type' to expected argument type 'TestType.Type'
Generic parameter 'TestType' could not be inferred
I feel blind, but I really don't see the problem.
Use TestProtocol.Type instead of TestType.Type, i.e.
func getTest(as type: TestProtocol.Type) {}
My understanding is that you want the function to know the class of the generic by using information from the enum, but generic functions need to be able to infer the type at compile time, and the result of TestEnum.type is only available at run time.
I'm not sure what the real case scenario you have, but I think that the best you can do is to have a function that returns TestProtocol, either as a type or object, whatever you need, and conditionally cast it to the type you want.
If you give us a more real-life example, we might be able to help you find a better solution.

Swift protocol with associated type and shadow type erasure

I need to pass an object which implements a protocol with associated type into a method, which accepts a protocol. This is not possible in Swift (up to the latest Swift 5) so I use type erasure with a shadow protocol based on this blog.
protocol ShadowA {
func test(data: Any) -> String
}
extension ShadowA {
func test(data: Any) -> String {
return "shadow"
}
}
protocol A: ShadowA {
associatedtype AT
func test(data: AT) -> String
}
class SpecificA: A {
typealias AT = Int
func test(data: Int) -> String {
return "specific"
}
}
Problem is that when I pass the object into the method then the "shadow's default implementation" is called instead of the "generic" one's. You can check the playground to see what is happening.
Is there something wrong or this use case is not possible in Swift at all?
This dynamic Dispatch and direct dispatch Magic for more explain about that check this swift-method-dispatch
Dynamic dispatch is the process of selecting which implementation of a
polymorphic operation (method or function) to call at run time.
Any method that implemented in protocol extension is Direct dispatched
i will explain more
because ShadowA is protocol and have default implementation in extension so compiler mind have this hint "Any class can adopt to this protocol without implement this method because it have default implementation"
Direct Dispatch
So at this Line What compiler know about test(data:Any) from item and because item is protocol with default implementation so it direct it to call direct default implementation
return "in method: \(item.test(data: 0))"
func passedIntoMethod(item: ShadowA) -> String {
return "in method: \(item.test(data: 0))"
}
Also At this line
let a2: ShadowA = SpecificA() // becuse compiler know only about a2 at this line that it is Protocol so it direct dispatch it
print(a2.test(data: 0))
Dynamic Dispatch
Here compiler know that a1 is concreate type so it call test Method that implemented inside it . if not implemented then it will call default implementation
let a1 = SpecificA()
print(a1.test(data: 0))

Swift Implementation generic function

I'm a developer on Java and I'm trying to write in Swift the same solution that I have in Java code.
Is it possible to do this on Swift?
Example Java:
public interface Converter<S,T> {
T convert(S in)
}
public class CarConverterToDTO implements Converter<Car, CarDTO> {
#Override
public CarDTO convert(Car in) {
.....
}
}
Example Swift:
protocol Converter {
func convert<IN, OUT>(in: IN) -> OUT
}
How it would be the implementation?
Thanks!!!
What appears to be a simple question is actually the tip of a rather large and unpleasant iceberg…
I'm going to start by giving you what is probably the real solution to your problem:
class Converter<Input, Output> {
func convert(_ input: Input) -> Output {
fatalError("subclass responsibility")
}
}
struct Car { }
struct CarDTO { }
class DTOCarConverter: Converter<Car, CarDTO> {
override func convert(_ input: Car) -> CarDTO {
return CarDTO()
}
}
Above, I've translated your Java Converter interface into a Swift class instead of a Swift protocol. That's probably what you want.
Now I'll explain why.
A programmer coming from Java to Swift might think that a Swift protocol is the equivalent of a Java interface. So you might write this:
protocol Converter {
associatedtype Input
associatedtype Output
func convert(_ input: Input) -> Output
}
struct Car { }
struct CarDTO { }
class /* or struct */ DTOCarConverter: Converter {
func convert(_ input: Car) -> CarDTO {
return CarDTO()
}
}
Okay, now you can create a converter and convert something:
let converter = DTOCarConverter()
let car = Car()
let dto = converter.convert(car)
But you're going to run into a problem as soon as you want to write a function that takes a Converter as an argument:
func useConverter(_ converter: Converter) { }
// ^
// error: protocol 'Converter' can only be used as a generic constraint because it has Self or associated type requirements
“Well, duh,” you say, “you forgot the type arguments!” But no, I didn't. Swift doesn't allow explicit type arguments after a protocol name:
func useConverter(_ converter: Converter<Car, CarDTO>) { }
// ^ ~~~~~~~~~~~~~
// error: cannot specialize non-generic type 'Converter'
I don't want to get into why you can't do this. Just accept that a Swift protocol is not generally equivalent to a Java interface.
A Swift protocol with no associated types and no mention of Self is, generally, equivalent to a non-generic Java interface. But a Swift protocol with associated types (or that mentions Self) is not really equivalent to any Java construct.
When discussing this problem, we often use the acronym “PAT”, which stands for “Protocol with Associated Types” (and includes protocols that mention Self). A PAT doesn't define a type that you can use as a function argument, return value, or property value. There's not much you can do with a PAT:
You can define a subprotocol. For example, Equatable is a PAT because it defines the == operator to take two arguments of type Self. Hashable is a subprotocol of Equatable.
You can use a PAT as a type constraint. For example, Set is a generic type. Set's type parameter is named Element. Set constrains its Element to be Hashable.
So you can't write a function that takes a plain Converter as an argument. But you can write a function that takes any implementation of Converter as an argument, by making the function generic:
func useConverter<MyConverter: Converter>(_ converter: MyConverter)
where MyConverter.Input == Car, MyConverter.Output == CarDTO
{ }
That compiles just fine. But sometimes it's inconvenient to make your function generic.
And there's another problem that this doesn't solve. You might want a container that holds various Converters from Car to CarDTO. That is, you might want something like this:
var converters: [Converter<Car, CarDTO>] = []
// ^ ~~~~~~~~~~~~~
// error: cannot specialize non-generic type 'Converter'
We can't fix this by making converters generic, like we did with the useConverter function.
What you end up needing is a “type-erased wrapper”. Note that “type-erased” here has a different meaning that Java's “type erasure”. In fact it's almost the opposite of Java's type erasure. Let me explain.
If you look in the Swift standard library, you'll find types whose names start with Any, like AnyCollection. These are mostly “type-erased wrappers” for PATs. An AnyCollection conforms to Collection (which is a PAT), and wraps any type that conforms to Collection. For example:
var carArray = Array<Car>()
let carDictionary = Dictionary<String, Car>()
let carValues = carDictionary.values
// carValues has type Dictionary<String, Car>.Values, which is not an array but conforms to Collection
// This doesn't compile:
carArray = carValues
// ^~~~~~~~~
// error: cannot assign value of type 'Dictionary<String, Car>.Values' to type '[Car]'
// But we can wrap both carArray and carValues in AnyCollection:
var anyCars: AnyCollection<Car> = AnyCollection(carArray)
anyCars = AnyCollection(carValues)
Note that we have to explicitly wrap our other collections in AnyCollection. The wrapping is not automatic.
Here's why I say this is almost the opposite of Java's type erasure:
Java preserves the generic type but erases the type parameter. A java.util.ArrayList<Car> in your source code turns into a java.util.ArrayList<_> at runtime, and a java.util.ArrayList<Truck> also becomes a java.util.ArrayList<_> at runtime. In both cases, we preserve the container type (ArrayList) but erase the element type (Car or Truck).
The Swift type-erasing wrapper erases the generic type but preserves the type parameter. We turn an Array<Car> into an AnyCollection<Car>. We also turn a Dictionary<String, Car>.Values into an AnyCollection<Car>. In both cases, we lose the original container type (Array or Dictionary.Values) but preserve the element type (Car).
So anyway, for your Converter type, one solution to storing Converters in a container is to write an AnyConverter type-erased wrapper. For example:
struct AnyConverter<Input, Output>: Converter {
init<Wrapped: Converter>(_ wrapped: Wrapped) where Wrapped.Input == Input, Wrapped.Output == Output {
self.convertFunction = { wrapped.convert($0) }
}
func convert(_ input: Input) -> Output { return convertFunction(input) }
private let convertFunction: (Input) -> Output
}
(There are multiple ways to implement type-erased wrappers. That is just one way.)
You can then use AnyConverter in property types and function arguments, like this:
var converters: [AnyConverter<Car, CarDTO>] = [AnyConverter(converter)]
func useConverters(_ converters: [AnyConverter<Car, CarDTO>]) {
let car = Car()
for c in converters {
print("dto = \(c.convert(car))")
}
}
But now you should ask: what's the point? Why bother making Converter a protocol at all, if I'm going to have to use a type-erased wrapper? Why not just use a base class to define the interface, with subclasses implementing it? Or a struct with some closures provided at initialization (like the AnyConverter example above)?
Sometimes, there's not a good reason to use a protocol, and it's more sensible to just use a class hierarchy or a struct. So you should take a good look at how you're implementing and using your Converter type and see if a non-protocol approach is simpler. If it is, try a design like I showed at the top of this answer: a base class defining the interface, and subclasses implementing it.
The Swift equivalent to your Java code looks like this.
protocol Converter {
associatedtype Input
associatedtype Output
func convert(input: Input) -> Output
}
class CarConverterToDTO: Converter {
typealias Input = Car
typealias Output = CarDTO
func convert(input: Car) -> CarDTO {
return CarDTO()
}
}
Explanation
The equivalent to a generic Java interface in Swift, would be a protocol with associatedtypes.
protocol Converter {
associatedtype Input
associatedtype Output
}
To create an implementation of that protocol, the implementation must specify which types the associated types maps to, using typealias.
class CarConverterToDTO: Converter {
typealias Input = Car
typealias Output = CarDTO
}
Type Erasure
If you try to use to this approach, you may run into the issue of trying to store an instance of your generic protocol in a variable or property, in which case you will get the compiler error:
protocol 'Converter' can only be used as a generic constraint because it has Self or associated type requirements
The way to solve this issue in Swift, is by using type erasure, where you create a new implementation of your generic protocol, that itself is a generic type (struct or class), and uses a constructor accepting a generic argument, matching your protocol, like so:
struct AnyConverter<Input, Output>: Converter {
// We don't need to specify type aliases for associated types, when the type
// itself has generic parameters, whose name matches the associated types.
/// A reference to the `convert(input:)` method of a converter.
private let _convert: (Input) -> Output
init<C>(_ converter: C) where C: Converter, C.Input == Input, C.Output == Output {
self._convert = converter.convert(input:)
}
func convert(input: Input) -> Output {
return self._convert(input)
}
}
This is usually accompanied by an extension function on the generic protocol, that performs the type erasure by creating an instance of AnyConverter<Input, Output> using self, like so:
extension Converter {
func asConverter() -> AnyConverter<Input, Output> {
return AnyConverter(self)
}
}
Using type erasure, you can now create code that accepts a generic Converter (by using AnyConverter<Input, Output>), that maps Car to CarDTO:
let car: Car = ...
let converter: AnyConverter<Car, CarDTO> = ...
let dto: CarDTO = converter.convert(input: car)

Storing a generic conforming to an associated type inside A collection

I am trying to store a generic who uses an an associated type, however when trying to create a type which should conform to the generic type I describe in the generic list at the top of class A, but I get the error.
"Cannot invoke 'append' with an argument list of type '(B)'"
How can I properly declare the generic so that this code works?
class A<DataType: Any, AssociatedType: Runable> where
AssociatedType.DataType == DataType {
var array = Array<AssociatedType>()
func addAssociatedValue(data: DataType) {
array.append(B(data: data))
}
func runOnAll(with data: DataType) {
for item in array {
item.run(with: data)
}
}
}
class B<DataType>: Runable {
init(data: DataType) { }
func run(with: DataType) { }
}
protocol Runable {
associatedtype DataType
func run(with: DataType)
}
I am also using Swift 4.2 so if there is a solution that uses one of the newer Swift features that will also work as a solution.
B conforms to Runnable, yes, but you can't put it into an array that's supposed to store AssociatedTypes. Because the actual type of AssociatedType is decided by the caller of the class, not the class itself. The class can't say, "I want AssociatedType to always be B". If that's the case, you might as well remove the AssociatedType generic parameter and replace it with B. The caller can make AssociatedType be Foo or Bar or anything conforming to Runnable. And now you are forcing to put a B in.
I think you should rethink your model a bit. Ask yourself whether you really want AssociatedType as a generic parameter.
You could consider adding another requirement for Runnable:
init(data: DataType)
And add required to B's initializer. This way, you could write addAssociatedValue like this:
func addAssociatedValue(data: DataType) {
array.append(AssociatedType(data: data))
}

Returning a generic from a protocol-defined function in Swift

I declared a protocol with a generic function, but it seems that the type inference isn't working properly after implementing it.
protocol SearchableRealmModel {
static func search<Self: Object>(needle: String) -> Results<Self>?
}
class Thing: Object, SearchableRealmModel {
class func search<Thing>(needle: String) -> Results<Thing>? {
return realm()?.objects(Thing).filter("name == '\(needle)'")
}
}
let things = Thing.search("hello") // works but inferred type is Results<Object>?
The problem here is that the inferred type of things is Results<Object>?. I realize these variations can be used,
let things: Results<Thing>? = Thing.search("hello")
let things = Thing.search("hello") as Results<Thing>?
but having to specify the type every time is quite repetitive.
In my tests, using other types than Results<..>? kept the type inference intact. And this could be caused by having to specify the parent class in Self: Object (which is required because of Results).
Any help is appreciated.
This is a limitation of Swift's generics machinery. The compiler can generate a concrete signature for static func search(needle: String) -> Results<Object>? which satisfies the type constraint because Object subclasses will match this. You could probably file a bug towards bugs.swift.org because I think the Swift core team would also consider this to be a bug, if not very unexpected behavior.
However, you can modify your code to use protocol extensions to do what you want:
protocol SearchableRealmModel {}
extension SearchableRealmModel where Self: Object {
static func search(needle: String) -> Results<Self> {
return try! Realm().objects(Self).filter("name == '\(needle)'")
}
}
class Thing: Object, SearchableRealmModel {
dynamic var name = ""
}
let result = Thing.search("thing1") // => inferred as Results<Thing>
print(result.first?.name)
If you want custom implementations of search for other Realm models, you can reimplement the function there, which the compiler will prioritize over the protocol extension version:
class OtherThing: Object, SearchableRealmModel {
dynamic var id = ""
static func search(needle: String) -> Results<OtherThing> {
return try! Realm().objects(OtherThing).filter("id == '\(needle)'")
}
}