Skip to content
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

uncurry store update function and drop Option wrapper around effects #224

Merged
merged 5 commits into from
Feb 28, 2024

Conversation

buntec
Copy link
Owner

@buntec buntec commented Feb 26, 2024

No description provided.

store.dispatch(SetActivity(activity.some))
)
)
(_, _) match {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could avoid this I think since case matches at the top level of a function are allowed (due to the questionable PartialFunction Function subclass relationship)

case Inc(amount) =>
state => state.copy(counter = state.counter + amount) -> none
case Reset => _.copy(counter = 0) -> none
(_, _) match {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can omit this I think.

_ match {
case SetFruit(fruit) => _.copy(fruit = fruit) -> none
case Inc => state => state.copy(counter = state.counter + 1) -> none
(_, _) match {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, could be:

_ => 
  {
     case (SetFruit(fruit), state) => state.copy(fruit = fruit) -> F.unit
     case (Inc, state) => state.copy(counter = state.counter + 1) -> F.unit
}

I think.

@@ -44,9 +43,9 @@ class App[F[_]](implicit val F: Async[F]) extends ff4s.App[F, State, Action] {
override val store = for {

store <- ff4s.Store[F, State, Action](State())(_ =>
Copy link

@johnynek johnynek Feb 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we want something like this:

def simple[F[_]: Concurrent, State, Action](init: State)(update: (Action, State) => State): Store[F, State, Action] = {
  val unit = Concurrent[F].unit
  apply[F, State, Action](init) { _ => { (a, s) => (update(a, s), unit) } }
}

Then I think many simple apps can become something like:

store <- ff4s.Store.simple[F, State, Action](State())) {
  case (Action.Toggle(), state) => state.copy(toggle = !state.toggle)
}

or similar.

Also, adding a method def flipToggle: State to your case class State this example becomes even cleaner:

store <- ff4s.Store.simple[F, State, Action](State())) {
  case (Action.Toggle(), state) => state.flipToggle
}

.modify(state => update(action, state))
.flatMap(fu =>
Applicative[F]
.unlessA(fu == Applicative[F].unit)(supervisor.supervise(fu))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make one call to unit and internally cache. For internal code of a library, I bias towards avoiding allocations. In fact here I would write:

.flatMap { fu =>
  if (fu == unit) unit
  else supervisor.supervise(fu)
}

so the additional cost in the case of non-unit effect is 1 additional .equals and 1 additional branch (which possibly a branch predictor would basically wind up hiding the cost of.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand: if we know that unit is a val for cats.effect.IO (and presumably any other reasonable implementation), then where is the allocation? Also, I think I prefer keeping unlessA b/c it makes the intent clear and the cost is just one additional function call (hopefully inlined by the JIT).

case Action.SetTodoInput(what) =>
_.copy(todoInput = Some(what)) -> none
case (Action.SetTodoInput(what), state) =>
state.copy(todoInput = Some(what)) -> F.unit

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

many (most?) of the examples are of the simple kind I mentioned above. I think having a helper on Store to support this would be really nice.

Copy link

@johnynek johnynek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really great to me.

.modify(state => update(action, state))
.flatMap(fu =>
Applicative[F]
.unlessA(fu == Applicative[F].unit)(supervisor.supervise(fu))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second argument is a call by name param which requires an allocation but it's probably not a big deal if you much prefer it.

@@ -75,12 +76,21 @@ sealed trait Store[F[_], State, Action] {

object Store {

/** A simple type of store where all actions are pure state updates.
*/
def pure[F[_]: Concurrent, State, Action](init: State)(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. I like the name too. Maybe it would be a a good idea to use it in one or two of your existing examples?

@buntec
Copy link
Owner Author

buntec commented Feb 28, 2024

Thanks so much for this!

@buntec buntec merged commit 889930f into main Feb 28, 2024
15 checks passed
@buntec buntec deleted the change-store-function-signature branch February 28, 2024 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants