@ExFalsoQuodlibet So just to be clear, you're saying this code:
let prependWorkersHomeStreet =
\User.Lenses.workplace.workers.traverse.home.street %~ { "home " + $0 }
// some function where I'm describing state transitions:
modify(\State.Lenses.user %~ prependWorkersHomeStreet)
would be more ergonomic and better represented by the vanilla Swift:
extension User {
mutating func prependWorkersHomeStreet() {
for index in workplace.workers.indices {
workplace.workers[index].home.street = "home \(workplace.workers[index].home.street)"
}
}
}
// some function where I'm describing state transitions:
modify { state in
state.user.prependWorkersHomeStreet()
}
I see a few issues with this:
- We've reverted to C-style programming of imperatively tracking indices. This is the exact thing FP languages have been generally avoiding since the 90s, and even now essentially all languages, FP or not, prefer the use of HOFs over manual index tracking.
- It's 261 characters instead of 157 (66% increase) and 10 lines instead of 3.
- Now when I want to call my modifications, I need to pass the argument in with
&
, and the binding now needs to be avar
ready for mutation. If I didn't want to mutate the original (I generally don't) or if it's a let (it generally is), I need to create a temporary var to hold this mutation, which won't compose well with function application. You can't just call:
someFn(modifications.map { $0(state) })
you need to instead:
var states: [State] = []
for m in modifications {
var s = state
m(&s)
states.append(s)
}
someFn(states)
which feels like writing Java code from 2005 (if Java had first class functions). To be fair to the imperative code, it's better suited for reductions than mapping, and that is a common enough usecase too:
someFn <| modifications.reduce(state) { $1($0) }
vs
var s = state
for m in modifications {
m(&s)
}
someFn(s)
but even in this best case for imperative code, I still prefer the functional solution (and it's still fewer characters and lines).
It's not that the code you sent is particularly complicated or long, but it is very imperative and relatively much longer. Scaling this up is part of the reason imperative code bases end up being much larger than FP ones in terms of LOC and character count.
It seems you're not sold on the idea of FP and related concepts (purity, HOFs, etc.) over imperative programming. That's an entirely orthogonal discussion to this post, and one that would take a while to hash out. I won't be swayed away from lenses by simply seeing longer, imperative alternatives though.