Focus icon indicating copy to clipboard operation
Focus copied to clipboard

Optional traversal

Open marcprux opened this issue 8 years ago • 6 comments

Is there no way to lens through an optional? I've tried with Party.lpartyCaterer() • User.userName and Party.lpartyCaterer() • _Some • User.userName, to no avail.

// A party has a host, an optional caterer and an array of guests
class Party {
    var host : User
    var caterer : User?
    var guests : [User]

    init(h : User, c : User? = nil, g : [User] = []) {
        host = h
        caterer = c
        guests = g
    }

    class func lpartyHost() -> Lens<Party, Party, User, User> {
        let getter = { (party : Party) -> User in party.host }
        let setter = { (party : Party, host : User) -> Party in Party(h: host, c: party.caterer, g: party.guests) }
        return Lens(get: getter, set: setter)
    }

    class func lpartyCaterer() -> Lens<Party, Party, User?, User?> {
        let getter = { (party : Party) -> User? in party.caterer }
        let setter = { (party : Party, caterer : User?) -> Party in Party(h: party.host, c: caterer, g: party.guests) }
        return Lens(get: getter, set: setter)
    }

    class func lpartyGuests() -> Lens<Party, Party, [User], [User]> {
        let getter = { (party : Party) -> [User] in party.guests }
        let setter = { (party : Party, guests : [User]) -> Party in Party(h: party.host, c: party.caterer, g: guests) }
        return Lens(get: getter, set: setter)
    }
}

class PartySpec : XCTestCase {
    func testLens() {
        let party = Party(h: User("max", 1, [], "one"))
        let hostnameLens = Party.lpartyHost() • User.userName

        XCTAssert(hostnameLens.get(party) == "max")

        let updatedParty: Party = (Party.lpartyHost() • User.userName).set(party, "Max")
        XCTAssert(hostnameLens.get(updatedParty) == "Max")

        let catererLens = Party.lpartyCaterer() • User.userName

        let cateredParty: Party = (Party.lpartyCaterer() • User.userName).set(party, "Marc")
        XCTAssert(catererLens.get(updatedParty) == "Marc")

    }
}

marcprux avatar Apr 29 '16 21:04 marcprux

A Lens is not a Prism (in fact, they are sort of categorically dual), of which _Some() must be because T? -> T is, by its very nature, a partial operation. You need a prism hierarchy for this, not a lens hierarchy.

CodaFi avatar Apr 29 '16 21:04 CodaFi

Or you could (though I definitely don't recommend this) define the type of a forcing lens for Optionals. Maybe call it _UnsafeSome, then use that as your intermediary. Otherwise, because Swift doesn't let us express the variance of types, or treat this as a valid coercion, you have to rewrite the hierarchy.

CodaFi avatar Apr 29 '16 21:04 CodaFi

I'd expect a Lens composed with a Prism to be a Focus.Optional, but it looks like it isn't implemented: #4 😞

Is Optional unimplemented due to Swift language shortcomings, or just because no one has gotten around to it yet?

marcprux avatar Apr 30 '16 13:04 marcprux

Little of one, little of the other. A lot of the reason we haven't implemented things is because it's just not worth it yet. A lot of the power of the Lens library is things generalize in a thousand little ways where in Swift you're lucky to get one.

CodaFi avatar Apr 30 '16 19:04 CodaFi

Do you foresee any of the planned improvements in Swift 3 making the implementation more worthwhile?

marcprux avatar Apr 30 '16 21:04 marcprux

Nope. Needs HKTs, which also necessitates a huge number of proposals that would fundamentally alter the language.

CodaFi avatar Apr 30 '16 23:04 CodaFi