How to write a generic function that converts from one generic protocol to another?

I'm not sure how to express a map operator's (using <^> rather than the traditional <$> as <$> doesn't compile) type signature in a way the compiler will understand. For the transformer operand the compiler complains: Cannot convert value of type '(T.Inner) throws -> U.Inner' to expected argument type '(_) throws -> _' Where the Inner associated type is the wrapped value of the Functor.

I'm completely new to Swift and as a learning exercise I'm trying to build a Result type, a Functor protocol, and a map operator. I'm hoping the formatting is okay. My goal was to have a Result or Either type that could work like the try? operator but would return the error if an exception was thrown. Everything seems to work except for the actual operator. This is for my own learning, I understand a Result type in coming in Swift 5. Thank you for any help.

import Foundation

public protocol Functor {
    associatedtype Inner
    associatedtype Outer: Functor
    func map<U>(
        _ transform: (Inner) throws -> U
        ) rethrows -> Outer where Outer.Inner == U
}

precedencegroup MapPrecedence {
    associativity: left
    higherThan: AssignmentPrecedence
}

infix operator <^> : MapPrecedence

public func <^><T: Functor, U: Functor>(
    _ transform: (T.Inner) throws -> U.Inner, rhs: T
    ) rethrows -> U {
        return rhs.map(transform)
}

:stop_sign: Cannot convert value of type '(T.Inner) throws -> U.Inner' to expected argument type '(_) throws -> _'

extension Optional: Functor {
    public typealias Inner = Wrapped
    public typealias Outer = Optional
}

extension Array: Functor {
    public typealias Inner = Element
    public typealias Outer = Array
}

public enum Result<Wrapped>: Functor {
    public typealias Inner = Wrapped
    public typealias Outer = Result
    
    case success(Wrapped)
    case error(Error)

    public static func `try`(
        _ runable: @autoclosure () throws -> Wrapped
        ) -> Result<Wrapped> {
        do {
            return .success(try runable())
        } catch {
            return .error(error)
        }
    }
    
    public func map<U>(
        _ transform: (Inner) throws -> U
        ) rethrows -> Result<U> {
            switch self {
            case .success(let val):
                return .success(try transform(val))
            case .error(let err):
                return .error(err)
            }
    }
}

I'm not sure what you're trying to do is doable in Swift, since you seem to also want Outer type to be generic.

If you try

print([Int].self, [Int].Outer.self)
// Array<Int> Array<Int>

you'll see that [Int].Outer type has already been inferred to be [Int], not generic Array.

Nevertheless, the <^> has some incorrect generic constraint, try this:

public func <^><T: Functor, U>(
    _ transform: (T.Inner) throws -> U, rhs: T
    ) rethrows -> T.Outer where U == T.Outer.Inner {
        return try rhs.map(transform)
}

When compilers can't convert certain types to another with unspecified type(_), it usually means that it can't infer the type from current generic constraint (in this case U == T.Outer.Inner); you'll need to provide more constraint or specify the type.

P.S. If you shorten the constraint you'll see what's wrong with the code

public protocol Functor {
    associatedtype Inner
    associatedtype Outer: Functor

    func map(
        _ transform: (Inner) throws -> Outer.Inner
        ) rethrows -> Outer
}

public func <^><T: Functor>(
    _ transform: (T.Inner) throws -> T.Outer.Inner, rhs: T
    ) rethrows -> T.Outer {
        return try rhs.map(transform)
}
Terms of Service

Privacy Policy

Cookie Policy