loganwright/Genome

Ambiguous reference to member 'transformFromJson' when using Optional property

NinoScript opened this issue · 3 comments

I have an optional property that I'm trying to transform

init(map: Map) throws {
    try logo = <~?map["logo"].transformFromJson(urlFromString)
}

It works great with non-optional properties, I'm guessing it can't decide which version of the generic transformFromJson method to use.

Hi @NinoScript

tl;dr

When using a transformer, <~? isn't necessary since the type is inferred from the closure. Use:

init(map: Map) throws {
    try logo = <~map["logo"].transformFromJson(urlFromString)
}

Why

The problem is that the <~? operator is reserved for one very specific scenario when mapping to optionals w/o transforms. I worked hard to get them out completely, but the problem actually stems from the generic system with basic types like Int, String, [Int], etc. Because of this, all optional properties were marked w/ <~? to aid in clarity.

I am working on a separate branch now that omits the <~? operator completely and will hopefully be better prepared to support more types when generic extensions can also conform to protocols.

Let me know if you have any more problems,

Logan

Hey, thank you for the swift response. (sorry, couldn't resist)

I just tried it with <~ instead, and it doesn't complain, but it just fails instead of returning the struct with a nil value.
Here's a playground showing the problem

//: Playground - noun: a place where people can play

import Foundation
import Genome

struct MaybeURL {
    let url: NSURL?
}

extension MaybeURL: StandardMappable {
    init(map: Map) throws { // neither alternative works
        try url = <~map["url"].transformFromJson(urlFromString)
//        try url = <~map["url"].transformFromJson(optUrlFromString)
    }
}

func urlFromString(s: String) throws -> NSURL {
    return try NSURL(string: s).unwrap()
}
func optUrlFromString(s: String) throws -> NSURL? {
    return NSURL(string: s)
}

enum UnwrappingException: ErrorType { case CouldNotUnwrap }
extension Optional {
    func unwrap() throws -> Wrapped {
        switch self {
        case .Some(let wrapped):
            return wrapped
        case .None:
            throw UnwrappingException.CouldNotUnwrap
        }
    }
}

extension StandardMappable {
    func sequence(map: Map) throws {}
}


let json: JSON = ["url": NSNull()]
guard let maybeURL = try? MaybeURL.mappedInstance(json) else {
    fatalError() // Fails! :(
}

assert(maybeURL.url == nil)
print("yes!")

Hey @NinoScript

haha ... I feel perfectly comfortable with this level of pun! 😄

The issue here is that essentially, the framework does type checks everywhere and throws errors whenever a contract is broken.

So, with your implementation here:

func urlFromString(s: String) throws -> NSURL {
    return try NSURL(string: s).unwrap()
}

Essentially takes a contract of: String -> NSURL. So, when Genome can't find an object of type String, an error is thrown. This includes null, or absence of value since technically they can't be String, so the framework can't know what value it should pass.

If however, the contract is changed so that it requires String? -> NSURL?, it gives the transformer an opportunity to handle nil cases.

func urlFromString(s: String?) throws -> NSURL? {
    guard let s = s else { return nil }
    return try NSURL(string: s).unwrap()
}

I hope that makes sense, happy to chat about it some more.

  • Logan