-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Proposal] Composable Reducers using Lenses and Prisms #171
Conversation
} | ||
} | ||
|
||
let mainReducer: Reducer<MainAction, MainState> = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this what will happen with the mainReducer
? It will call the sub reducers to take care of their respective parts and then translate their results into the MainState
(which is yet to be defined by the lens/prism implementations), right?
Is it different essentially from breaking reducer into smaller functions?
func reducer(state: State, event: Event) -> Event {
switch event {
case .sub1(sub1Event):
return state.set(\.sub1, reduceSub1(state: state.sub1, event: sub1Event))
...
}
}
func reduceSub1(state: Sub1State, event: Sub1Action) -> Sub1State {
switch event { ... }
}
Maybe seeing how this will fit into splitting view models (if it will) and with feedbacks will make this difference more appealing than for just reducers on their own?
Comparing two different approaches side by side will also probably make the difference more visual.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By introducing "Composable Reducers", we don't have to write reducer
(main reducer) pattern-matching, which we don't want to repeat things over and over again.
I also added "Composable Feedbacks" example in 4db5676 , so it becomes more apparent if we rewrite mainFeedback
by hand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd emphasise even more this point in the proposal itself, being one of the most powerful advantages of FP compositionality. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But don't we write the same pattern matching in the prisms implementations? =) It might be a bit easier to code generate, but until it is in place we'll have to write this boilerplate 🤷♂ I like how prisms look simple and separate concerns and I used them (in a bit different form) myself, but not sure what boilerplate will be more clear.
P.S. I'm 💯 into code generating this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ilyapuchka
Pattern-matching in small Prism
layer and making its composition is more FP way than pattern-matching whole gigantic enum at once filled with un-reusable code.
It becomes more and more apparent once we have n-layered reducers / feedbacks.
(Though it may sound unrealistic, IMO we should be ready for such scalable architecture)
And I totally agree that this proposal gains more popularity once we introduce codegen.
But even without it, I think it's worth introducing Lens
/ Prism
for scalability.
I personally can't live without these features to split large codebase.
(Currently ongoing in https://github.com/Babylonpartners/babylon-ios/pull/7938 )
And unfortunately, current Swift KeyPath
can't solve this problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm all into splitting huge switch into smaller pieces, that should be probably emphasised in the proposal as a win with pros and cons of each approach (I'm not yet sold thought that it will improve reusability on practice due to how types depend on each other in our code base) 👍
I think it will worth also adding your thoughts about why KeyPaths don't fit in the proposal, good candidate for Alternatives Considered section 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
47bde8b Added composition example to explain the simpler solution.
4db5676 |
Co-Authored-By: Ilya Puchka <ilyapuchka@gmail.com>
Co-Authored-By: Ilya Puchka <ilyapuchka@gmail.com>
Co-Authored-By: Giorgos Tsiapaliokas <giorgos.tsiapaliokas@mykolab.com>
|
||
/// For accessing enum cases. | ||
/// e.g. Whole = all possible enum cases, B = partial case | ||
struct Prism<Whole, Part> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are preview
and review
standard lingo for a Prism
? I know that Brandon used these names in his presentation but I don’t think they describe the purpose of the two functions very well. To me, subspace
and update
reads more clearly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Brandon followed Haskell's naming for those: http://hackage.haskell.org/package/lens-4.17.1/docs/Control-Lens-Prism.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zzcgumn
Yes, preview
and review
are commonly used names in Haskell.
There are some alternatives for Prism
method names which may be more intuitive:
-
Scala Monocle uses
getOption: Whole => Option[Part]
andreverseGet (aka apply): Part => Whole
https://julien-truffaut.github.io/Monocle/optics/prism.html -
tryGet: Whole -> Part?
andinject: Part -> Whole
is introduced here
https://broomburgo.github.io/fun-ios/post/lenses-and-prisms-in-swift-a-pragmatic-approach/
I'm OK with renaming too (I personally prefer 2.), but we might want to stay as is, since there are many more upcoming layers in these functional Optics family as described in
http://oleg.fi/gists/posts/2017-04-18-glassery.html
So renaming each of them may probably consume our brain too much that it's often better to just borrow the same names from original.
Could you please provide an example that illustrates why this approach is preferable compared to defining a couple of SubViewModels? I can see that this proposal adds good value if it allows us to create more of a plug and play architecture where the app the content is effectively calculated at runtime. Examples could be building a tab bar dynamically by something like |
```swift | ||
let reducer: Reducer<Action, State> = | ||
sub3Reducer | ||
.lift(action: .sub3Event >>> .sub2Event >>> .sub1Event) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't you think though that this will make debugging more complicated as it will add extra steps to reach the sub reducer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ilyapuchka
Not sure why you feel it's harder to debug 🙄
Do you want to add a breakpoint in the intermediate reducer that never appear in this code?
I think adding breakpoints in sub3Reducer
is sufficient because we aren't interested in intermediate reducers at all.
cf. https://github.com/inamiy/FunOptics for lightweight Optics library. |
Closing due to inactivity. Please re-open when you have time to work on it. ❤️ |
@RuiAAPeres Jokes aside, I will reopen this when more huge RAFs become needed in our project. |
cf. https://github.com/inamiy/Harvest-SwiftUI-Gallery (Example App) Here's my take for Composable Architecture using Optics (Lenses and Prisms) which this PR is proposing. |
I need this on "my" project. ❤️ Jokes aside, if there's no interest in merging this back to the official RAF, I will happily use whatever you come up with in your own side project (as long it uses RAS as base). 😉 |
@RuiAAPeres Also, choosing what Optics library to use is another topic to consider. |
cf. https://twitter.com/inamiy/status/1190512992640651264 Proof of concept for how the final composed reducer will look like (example). |
I am not convinced with the current API. 😓
On Sat, 2 Nov 2019 at 23:13, Yasuhiro Inami ***@***.***> wrote:
cf. https://twitter.com/inamiy/status/1190512992640651264
Proof of concept for how the final composed reducer will look like
(example).
[image: nef 2019-11-02 14 58 46]
<https://user-images.githubusercontent.com/138476/68078017-a228e900-fe11-11e9-9559-afe76e9f5a78.png>
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#171?email_source=notifications&email_token=AAHHYJREXIE2WX7DOTMQZLLQRYCQPA5CNFSM4H4UEDQKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEC5HBIA#issuecomment-549089440>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAHHYJTVSTXC7QLFF36BJHTQRYCQPANCNFSM4H4UEDQA>
.
--
*Rui Peres*
Lead iOS Engineer
|
This proposal introduces
Lens
andPrism
to break down a large ReactiveFeedbackreducer
/feedback
/state
/event
into small pieces.Please see @mbrandonw 's talk for more detail:
Brandon Williams - Composable Reducers & Effects Systems - YouTube