Swift Generic Functions, Protocols and associatedType - Cannot invoke function with an argument list of type '(from: T)' - swift

I am trying to generalise a functions for different types of objects that does the same things (retrieves a value from the objects using a keyPath).
class GenericOutputParameter<Type>: Equatable {
// Single Line Parameter for the Deal parameters
var path: WritableKeyPathApplicator<Type>
var label: String
var value: Any? // THIS IS OPTIONAL
var format: Int
var columnID: String
var order: Int
init(path: WritableKeyPathApplicator<Type>, label: String, value: Any?, format: Int, order: Int, columnID: String) {
self.path = path
self.label = label
self.value = value
self.format = format
self.order = order
self.columnID = columnID
}
}
protocol haveOutputs {
associatedtype containerType
var dictionary: [String : (path: WritableKeyPathApplicator<containerType>,label: String, value: Any, format: Int, order: Int)] { get set }
var outputs: [GenericOutputParameter<containerType>] { get set }
}
func fillOutputs<T: haveOutputs>(container: inout T) {
container.outputs.removeAll()
for (columnID, valueTuple) in container.dictionary {
container.outputs.append(GenericOutputParameter(path: valueTuple.path, label: valueTuple.label, value: valueTuple.path.retrieve(from: container), format: valueTuple.format,order: valueTuple.order, columnID: columnID))
}
container.outputs.sort(by: { $0.order < $1.order })
} // END OF FILLOUTPUTS
I am using associatedType in the protocol as each object has its own different dictionary.
The function fillOutputs(container: inout T) retrieves the value from the object for the parameter specified by the key paths and appends it to an array.
I am getting an error in the container.outputs.append line towards the end of the code, as follows: Cannot invoke 'retrieve' with an argument list of type '(from: T)'. This refers to retrieve(from: container). Before attempting to generalise, this function was a method of each object (container) and using retrieve(from: self) worked.
For reference, the retrieve method is part of another generic function:
class WritableKeyPathApplicator<Type> {
private let applicator: (Type, Any) -> Type
private let retriever: (Type) -> Any
init<ValueType>(_ keyPath: WritableKeyPath<Type, ValueType>) {
applicator = {
...
}
retriever = {
...
}
}
func apply(value: Any, to: Type) -> Type {
return applicator(to, value)
}
func retrieve(from: Type) -> Any {
return retriever(from)
}
}
Given I am not an expert on Swift nor fully comprehend protocols, I may have lost myself in a glass of water and I would appreciate any thought/help. Thanks

Related

Typescript Generic chained function to Swift

I'm really struggling to comprehend how to (if even possible) to convert a generics function written in Typescript into something I can use in Swift.
export type Filter<T> = (value: T) => boolean
export function isKeyEqualToValue<T>(key: keyof T) {
return function (value: T[keyof T]): Filter<T> {
return (object: T) => object[key] === value
}
}
isKeyEqualToValue<T>('key')(someObject.key)
const filters = userFilters.map(userFilterSet => isEvery(buildAlertFilter(userFilterSet)))
const isMatch = isAny(filters)
return flow.reduce((feed: String[], obj: SomeType) => {
if (!isMatch(obj)) return feed
return [
...feed,
{
...obj
},
]
}, [])
}
I would like to be able to input a struct model in for T and check if the inputted value matches the key. Would greatly appreciate some guidance here!
EDIT:
I've added how the method is being called and used. Essentially I'm trying to avoid doing an algorithm O(n)^2 and so I'm trying to build a list of filters based on our user's choice. Then cross check the bulk of my data (SomeType) with those built filters.
I'm working to translate another function using the similar principles.
export function hasInArray<T>(key: keyof T) {
return function (values: Array<any>): Filter<T> {
return (object: T) => values.includes((object[key] as unknown) as string)
}
}
This is what I have so far.
func notInArray<Root, Value>(for keyPath: KeyPath<Root, Value>) -> (Array<Any>) -> Filter<Root, Value> {
{ values in { object in !values.contains(where: object[keyPath: keyPath]) } } }
You haven't given how you expect to use this, so I need to make some assumptions. I'm assuming the TypeScript that calls this looks like this:
interface Person {
name: string;
age: number;
}
const key: keyof Person = "name";
const nameTester = isKeyEqualToValue(key);
const person = {name: "Alice", age: 23};
const result = nameTester("Alice")(person);
The equivalent to TypeScript's keyof in Swift is KeyPath. Keeping this as close to the TypeScript syntax as possible to make it easier to see how it maps, this would look like:
typealias Filter<T> = (_ value: T) -> Bool
func isKeyEqualToValue<T, Value>(key: KeyPath<T, Value>) -> (Value) -> (T) -> Bool
where Value: Equatable
{
return { (value: Value) -> Filter<T> in
return { (object: T) in object[keyPath: key] == value }
}
}
struct Person: Equatable {
var name: String
var age: Int
}
let key = \Person.name
let nameTester = isKeyEqualToValue(key: key)
let person = Person(name: "Alice", age: 23)
let result = nameTester("Alice")(person)
To make it better Swift (rather than matching the TypeScript so closely), it would look like:
typealias Filter<Root, Value: Equatable> = (Value) -> (Root) -> Bool
func isEqualToValue<Root, Value>(for keyPath: KeyPath<Root, Value>) -> Filter<Root, Value>
{
{ value in { object in object[keyPath: keyPath] == value } }
}
let nameTester = isEqualToValue(for: key)
Your second example is like the first.
func hasInArray<Root, Values>(for keyPath: KeyPath<Root, Values>) -> (Values.Element) -> (Root) -> Bool
where Values: Sequence, Values.Element: Equatable
{
{ value in { object in object[keyPath: keyPath].contains(value) } }
}
You will almost never want Array<Any>. You need an array of the specific element. But in this case you don't need an array at all; you just need any Sequence.
All this said, I wouldn't do it this way. I think it's much easier to understand if you create a Filter type to manage it.
// A Filter object over a specific Target object (for example, a Person)
struct Filter<Target> {
let passes: (Target) -> Bool
}
// Filters can be created many ways
extension Filter {
// By properties equal to a value
static func keyPath<Value>(_ keyPath: KeyPath<Target, Value>, equals value: Value) -> Filter
where Value: Equatable
{
Filter { target in
target[keyPath: keyPath] == value
}
}
// By properties containing a value
static func keyPath<Seq>(_ keyPath: KeyPath<Target, Seq>, contains value: Seq.Element) -> Filter
where Seq: Sequence, Seq.Element: Equatable
{
Filter { target in
target[keyPath: keyPath].contains(value)
}
}
// By a property being a member of a sequence
static func keyPath<Seq>(_ keyPath: KeyPath<Target, Seq.Element>, isElementOf seq: Seq) -> Filter
where Seq: Sequence, Seq.Element: Equatable
{
Filter { target in
seq.contains(target[keyPath: keyPath])
}
}
// By combining other filters
static func all(of filters: [Filter]) -> Filter {
Filter { target in
filters.allSatisfy { filter in filter.passes(target) }
}
}
}
struct Person {
var name: String
var age: Int
var children: [String]
}
let filter: Filter<Person> = .all(of: [
.keyPath(\.name, equals: "Alice"),
.keyPath(\.children, contains: "Bob"),
.keyPath(\.age, isElementOf: [23, 43]),
])
let alice = Person(name: "Alice", age: 23, children: ["Bob"])
let shouldInclude = filter.passes(alice) // true

Accessing Typed Value of Generic Argument in Swift

I am trying to make a dispatch function which can take Payload as an argument. The Payload can be Int, String or any other type. I tried the following approaches but inside the dispatch function the payload.value is always T and not Int or String. Casting is an option but I thought that was the whole point of generics.
struct Payload<T> {
let value: T
}
func dispatch<T>(payload: Payload<T>) {
print(payload.value) // get the value as Int, String or some other type
}
let payload = Payload<Int>(value: 100)
let payload2 = Payload<String>(value: "FOO")
dispatch(payload: payload)
dispatch(payload: payload2)
As you already know T is unconstrained so it can be any type. Your only option is to cast the value to the types you are expecting. You can simply switch the value:
switch payload.value {
case let value as Int:
print("Int:", value)
case let value as String:
print("String:", value)
default:
print("some other type", payload.value)
}
Depends on what you want to get inside your dispatch, you can create a protocol and requires T to be conformed to that protocol.
protocol Printable {
func printValue() -> String
}
struct Payload<T> {
let value: T
}
func dispatch<T: Printable>(payload: Payload<T>) {
print(payload.value.printValue()) // get the value as Int, String or some other type
}
extension String: Printable {
func printValue() -> String {
return self
}
}
extension Int: Printable {
func printValue() -> String {
return "\(self)"
}
}

Returning the associatedtype of an opaque return type

I have a simple protocol with an associated type, and a protocol extension that returns an array of this type.
protocol Foo {
associatedtype Unit
}
extension Foo {
var allTheFoos: [Unit] {
return []
}
}
I then have a struct which returns some Foo in a computed property, and another computed property that returns the allTheFoos array.
struct FakeFoo: Foo {
typealias Unit = Int
}
struct FooFactory {
var myFoo: some Foo {
return FakeFoo()
}
/* WHICH RETURN TYPE WILL
PLEASE THE SWIFT GODS?!
*/
var allTheFoos: [Foo.Unit] {
return myFoo.allTheFoos
}
}
The return type of allTheFoos matches Xcode's autocomplete type suggestion for the myFoo.allTheFoos call, but understandably, this yields a:
// var allTheFoos: [Foo.Unit] {}
ERROR: Associated type 'Unit' can only be used with a concrete type or generic parameter base
My question is: What return type will make Xcode happy?
Below are my attempts, and their corresponding errors
// var allTheFoos: [some Foo.Unit] {}
ERROR: 'some' types are only implemented for the declared type of properties and subscripts and the return type of functions
// func allTheFoos() -> some [Foo.Unit]
ERROR: Associated type 'Unit' can only be used with a concrete type or generic parameter base
// func allTheFoos<U: Foo.Unit>() -> [U]
ERROR: Associated type 'Unit' can only be used with a concrete type or generic parameter base
ERROR: Cannot convert return expression of type '[(some Foo).Unit]' to return type '[U]'
// func allTheFoos<U>() -> [U] where U: (some Foo).Unit
ERROR: 'some' types are only implemented for the declared type of properties and subscripts and the return type of functions
FYI: The reason I'm doing this in a computed property in the first place is to keep things clean in some SwiftUI code.
Thanks for any help you can give!
=========== UPDATE ===========
I missed some important stuff in my sample code, so to give some context: the code is used in a unit conversion app, so something that can turn Celsius into Kelvin, Kg into lbs, and anything else into anything else.
protocol Unit: Equatable {
var suffix: String { get }
}
struct Value<UnitType: Unit> {
let amount: Double
let unit: UnitType
var description: String {
let formatted = String(format: "%.2f", amount)
return "\(formatted)\(unit.suffix)"
}
}
Value is constrained to a unit type, so that it's not possible to convert Celsius into Litres.
Therefore, we have a Conversion protocol that stores all similar units together:
protocol Conversion {
associatedtype UnitType: Unit
var allUnits: [UnitType] { get }
func convert(value: Value<UnitType>, to unit: UnitType) -> Value<UnitType>
}
extension Conversion {
func allConversions(for value: Value<UnitType>) -> [Value<UnitType>] {
let units = self.allUnits.filter { $0 != value.unit }
return units.map { convert(value: value, to: $0) }
}
}
So an example of a conversion for Temperature would be:
struct Temperature: Conversion {
enum Units: String, Unit, CaseIterable {
case celsius, farenheit, kelvin
var suffix: String {
switch self {
case .celsius: return "˚C"
case .farenheit: return "˚F"
case .kelvin: return "K"
}
}
}
var allUnits: [Units] { return Units.allCases }
func convert(value: Value<Units>, to unit: Units) -> Value<Units> {
/* ... */
}
}
Finally, the actual app code where the problem occurs is here:
struct MyApp {
var current: some Conversion {
return Temperature()
}
// ERROR: Associated type 'UnitType' can only be used with a concrete type or generic parameter base
var allConversions: [Value<Conversion.UnitType>] {
// This value is grabbed from the UI
let amount = 100.0
let unit = current.allUnits.first!
let value = Value(amount: amount, unit: unit)
return current.allConversions(for: value)
}
}
Looking at how you've implemented AnyValue, I think what you want here is just:
var allConversions: [String] {
let units = self.allUnits.filter { $0 != value.unit }
return units.map { convert(value: value, to: $0) }.description
}
Or something like that. All the algorithms that match what you're describing are just "conversion -> string." If that's the case, all you really want is CustomStringConvertible.
Managed to solve this issue using some Type Erasure:
struct AnyValue {
let description: String
init<U: Unit>(_ value: Value<U>) {
self.description = value.description
}
}
allowing for:
var allConversions: [AnyValue] {
// This value is grabbed from the UI
let amount = 100.0
let unit = current.allUnits.first!
let value = Value(amount: amount, unit: unit)
return current.allConversions(for: value).map(AnyValue.init)
}
However, this feels like a clunky solution (and one that opaque return types was introduced to avoid). Is there a better way?

Is there a way in Swift to create a function that takes a Value.Protocol and returns a Value.Type?

I'm trying to create a Dependency Container, similar to Swinject, but I want to return the metatype '.Type' instead of a concrete type.
Is it even possible?
I've tried creating a basic concept to see if it would work.
class DependencyContainer {
var types: [ObjectIdentifier: Any] = [:]
func register<Product>(_ concreteType: Any.Type, to protoType: Product.Type) {
self.types[ObjectIdentifier(protoType)] = concreteType
}
func get<Product>(of type: Product.Type) -> Product.Type? {
return self.types[ObjectIdentifier(type)] as? Product.Type
}
}
protocol TestProduct {
var number: Int { get }
init(number: Int)
}
struct SomeTestProduct: TestProduct {
var number: Int
}
let container = DependencyContainer()
container.register(SomeTestProduct.self, to: TestProduct.self)
let testProduct = container.get(of: TestProduct.self)
I expected the get function to return a SomeTestProduct.Type as a TestProduct.Type, but instead it always returns nil because the compiler thinks I want a TestProduct.Protocol and fails the type cast.

Using protocol with associated type inside a generic function in Swift

Hi I'm trying to create a function which accepts a generic type that conforms to a specific protocol, and this protocol has a static builder that return a new instance of the same class (using associated type), after that he returns the new object that was created.
The generic function will return a list of the generic type.
In my efforts to make it compile, I found a solution, but I feel like I cheated, please see the following code:
import UIKit
protocol SomeRougeProtocol {
associatedtype U
static func convert(id: String) -> U
}
class FirstRougeClass: SomeRougeProtocol {
typealias U = FirstRougeClass
let value: String
init(value: String = "") {
self.value = value
}
static func convert(id: String) -> FirstRougeClass {
return FirstRougeClass(value: id)
}
}
class SecondRougeClass: SomeRougeProtocol {
typealias U = SecondRougeClass
let value: String
init(value: String = "") {
self.value = "special \(value)"
}
static func convert(id: String) -> SecondRougeClass {
return SecondRougeClass()
}
}
/// Takes type and generate an array from it.
func superConvert<T: SomeRougeProtocol>(class: T) -> [T.U] {
return [T.convert(id: "1"), T.convert(id: "2"), T.convert(id: "3")]
}
// *** This is the cheasty part, I have to create a disposable object to pass as input, it won't compile otherwise.
let disposableObject = FirstRougeClass()
let a: [FirstRougeClass] = superConvert(class: disposableObject)
a[0].value // Generates "1" in the playground, success!
My question is, if there is a better way to achieve what I done? without using a disposable object would be a big plus haha
Thanks!