I'm writing a library that is creating an implementation of lenses (inspired by Haskell). Currently, I've got an implementation of relatively simple lenses. You can generate lenses like so:
@makeLenses
struct Address {
let street: String
let city: String
let state: String
let zipCode: Int
}
@makeLenses
struct User {
let name: String
let age: Int
let address: Address
}
this will create static vars inside the structs that match the names of the fields. Ideally I would actually create the lenses at the same level as the struct instead of inside the struct, but this doesn't always work, you can't introduce arbitrary names in a macro at the global scope.
I currently have 3 variants of @makeLenses
.
@makeLenses
- this will create static vars inside the struct.@makeLensesPeer
- this will create static vars at the same level.@makeLensesPeerNonStatic
- this will create normal vars at the same level. Not really worth using with the current state of Swift Macros, which is why the name is unwieldy.
What the above code allows is code like so:
let changeStreet: (User) -> User =
User.address ~ Address.street .~ "new street name"
or if you are inside a struct and were able to do @makeLensesPeer
:
let changeStreet: (User) -> User =
address ~ street .~ "new street name"
as opposed to:
func changeStreet(_ user: User) -> User {
User(
name: user.name,
age: user.age,
address: Address(
street: "new street name",
city: user.address.city,
state: user.address.state,
zipCode: user.address.zipCode
)
)
}
My main issue with this currently is if you're adding lenses to another struct inside of some struct (say a State
struct inside of some Feature
), then what you can easily end up with is:
let changeStreet: (UserFeature.State) -> UserFeature.State =
UserFeature.State.address ~ AddressFeature.State.street .~ "new street name"
Which isn't nice. Assuming we aren't getting the arbitrary names at the global scope limitation lifted, my next best solution is to have a type Lens
with an alias L
, and then have a freestanding macro #makeLenses
create an extension to Lens
with static vars (with accessors), so I could get this code:
// at global scope
#makeLenses(UserFeature.State.self)
#makeLenses(AddressFeature.State.self)
// anywhere in the program
let changeStreet: (User) -> User =
L.address ~ L.street .~ "new street name"
Is this feasible given the current implementation of macros? If not, does anyone have alternative suggestions to easing use of lenses across the program?