cfischer
cfischer

Reputation: 24912

"type of expression is ambiguous without more context" error in Swift closure

I'm getting a strange type-related error in my Swift code:

type of expression is ambiguous without more context.

This happens even if I provide the full type information.

Here's the code that reproduces it.

I have 2 structures:

struct Person{
    let name_ : String
    let address_ : Address
}

struct Address {
    let street_ : String
    let city_ : String
}

I then create a structure that contains 2 functions to get and set the address of a Person:

struct Lens<A,B> {
    let extract: (A)->B
    let create: (B,A) -> A
}

When I try to create an instance of Lens that gets and sets the address (in the later case it returns a new Person with the new address), I get the error in the first closure.

let lens : Lens<Person, Address> =
Lens(
    extract: {(p:Person)->Address in
        return p.address_}, // here's the error
    create: {Person($0.name_,
        Address(street_: $1, city_: $0.address_.city_))})

Not only the type of the parameter of the first closure is specified in the type for lens, but also in the closure itself.

What's going on????

Upvotes: 4

Views: 2720

Answers (2)

Rob
Rob

Reputation: 437917

While it suggests the error is in extract, it really was in create. The $0 and $1 are backwards. You're referring to $0.name_, but $0 of the create closure is B, the Address, but name_ is a property of Person. I think you meant:

let lens : Lens<Person, Address> = Lens(
    extract: { $0.address_ },
    create: { Person(name_: $1.name_, address_: Address(street_: $0.street_, city_: $1.address_.city_)) }
)

Or you can redefine Lens:

struct Lens<A, B> {
    let extract: (A) -> B
    let create: (A, B) -> A   // note, A & B swapped
}

And then you can do:

let lens : Lens<Person, Address> = Lens(
    extract: { $0.address_ },
    create: { Person(name_: $0.name_, address_: Address(street_: $1.street_, city_: $0.address_.city_)) }
)

Or, perhaps you meant:

let lens : Lens<Person, Address> = Lens(
    extract: { $0.address_ },
    create: { Person(name_: $0.name_, address_: $1) }
)

That way, the create uses both the street and city of the supplied address. (It doesn't make sense to me to use the street of the address, but not the city.)

Upvotes: 3

dfrib
dfrib

Reputation: 73196

Your initialization of lens will work using a form like the following:

let lens = Lens<Person, Address>(
    extract: {p in p.address_},
    create: {(a,p) in Person(name_: p.name_, address_: Address(street_: a.street_, city_: p.address_.city_))})

As Martin R noted, the sole error were in fact in the create closure. When you call the default initializers for your struct, you need to supply the external names (=internal names) for all arguments (even the first one), by default (this differs from regular functions where you may omit the external name for the first function argument). Also make sure you supply the correct argument types for the initializer.

Note from above that you needn't supply the closure type ((p:Person)->Address in ...), as Swift can infer (and expects this) from the closure: p in ... suffices (see example above). Or, even more condensed, omit explicit variable names in you closures and make use of $0 and $1 (expected arguments) references instead, as in Robs solution.

Upvotes: 2

Related Questions