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