Skip to content

Instantly share code, notes, and snippets.

@FilipZawada
Last active February 27, 2020 00:06
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FilipZawada/934397bbef58e529762aff571a59d9b0 to your computer and use it in GitHub Desktop.
Save FilipZawada/934397bbef58e529762aff571a59d9b0 to your computer and use it in GitHub Desktop.
Generating Lenses helpers using Sourcery

What are Lenses? Great explanation by @mbrandonw

Files:

  • input.swift - sample structs for which we want helpers to be generated
  • lens.stencil - sourcery template to generate lenses helpers
  • output.swift - sample lenses helpers generated
  • zLens.swift - simple implementation of lenses, so you can play with it

Hints:

  • Usually you want to generate helpers only for certain structs in your project. To limit scope of sourcery changes, keep your relevant structs under one directory and just point sourcery to that directory instead of whole project.
struct House {
let rooms: Room
let address: String
let size: Int
}
struct Room {
let people: [Person]
let name: String
}
struct Person {
let name: String
}
{# This script assumes you follow swift naming convention, e.g. structs start with an upper letter #}
{# Launch using Sourcery: https://github.com/krzysztofzablocki/Sourcery #}
{% for type in types.structs %}
extension {{ type.name }} {
{% for variable in type.variables %}
static let {{ variable.name }}Lens = Lens<{{type.name}}, {{variable.type}}>(
get: { $0.{{variable.name}} },
set: { {{variable.name}}, {{type.name | lowercase}} in
{{type.name}}({% for argument in type.variables %}{{argument.name}}: {% if variable.name == argument.name %}{{variable.name}}{% else %}{{type.name || lowercase}}.{{argument.name}}{% endif %}{% if not forloop.last%}, {% endif %}{% endfor %})
}
){% endfor %}
}
{% endfor %}
// Generated using Sourcery 0.3.3 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
extension House {
static let roomsLens = Lens<House, Room>(
get: { $0.rooms },
set: { rooms, house in
House(rooms: rooms, address: house.address, size: house.size)
}
)
static let addressLens = Lens<House, String>(
get: { $0.address },
set: { address, house in
House(rooms: house.rooms, address: address, size: house.size)
}
)
static let sizeLens = Lens<House, Int>(
get: { $0.size },
set: { size, house in
House(rooms: house.rooms, address: house.address, size: size)
}
)
}
extension Person {
static let nameLens = Lens<Person, String>(
get: { $0.name },
set: { name, person in
Person(name: name)
}
)
}
extension Room {
static let peopleLens = Lens<Room, [Person]>(
get: { $0.people },
set: { people, room in
Room(people: people, name: room.name)
}
)
static let nameLens = Lens<Room, String>(
get: { $0.name },
set: { name, room in
Room(people: room.people, name: name)
}
)
}
// a Swift 3 version of Brandon's implementation: https://gist.github.com/mbrandonw/4acd26ab01bb6140af69
infix operator *~: MultiplicationPrecedence
infix operator |>: AdditionPrecedence
struct Lens<Whole, Part> {
let get: (Whole) -> Part
let set: (Part, Whole) -> Whole
}
func * <A, B, C> (lhs: Lens<A, B>, rhs: Lens<B, C>) -> Lens<A, C> {
return Lens<A, C>(
get: { a in rhs.get(lhs.get(a)) },
set: { (c, a) in lhs.set(rhs.set(c, lhs.get(a)), a) }
)
}
func *~ <A, B> (lhs: Lens<A, B>, rhs: B) -> (A) -> A {
return { a in lhs.set(rhs, a) }
}
func |> <A, B> (x: A, f: (A) -> B) -> B {
return f(x)
}
func |> <A, B, C> (f: @escaping (A) -> B, g: @escaping (B) -> C) -> (A) -> C {
return { g(f($0)) }
}
// file is prefixed with `z` to display it as a last file in gist BTW
@krzysztofzablocki
Copy link

You can also use phantom protocol or annotations to mark structs you want to generate lenses for, e.g.

// somewhere in your code-base
protocol AutoLense {}

// any struct / class you want to add lenses to
class Foo: AutoLense {}
// or even
extension MyStruct: AutoLense {} 

Then you can tweak this template to just do:

{% for type in types.implementing.AutoLense %}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment