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

add state-flow.api namespace #118

Merged
merged 5 commits into from
May 18, 2020
Merged

add state-flow.api namespace #118

merged 5 commits into from
May 18, 2020

Conversation

dchelimsky
Copy link
Contributor

@dchelimsky dchelimsky commented May 11, 2020

This is take 2 of #117, which originally started as "let's add fmap somewhere"

state-flow.api

Introduce a new state-flow.api namespace which imports the following vars:

;; from state-flow.core
flow
run
run*
log-and-throw-error!
ignore-error

;; from state-flow.state
get-state  ;; state/gets
swap-state ;; state/modify
invoke     ;; formerly wrap-fn
return
fmap       ;; new addition

;; from state-flow.cljtest
defflow

;; from state-flow.assertions.matcher-combinators/
match?

Deprecates

  • state/wrap-fn (replaced by state/invoke)

Also

  • improves/clarifies some docstrings

Rationale:

We wanted to add fmap and decided it made more sense in core than in state. While reviewing this, we realized that state/return and state/wrap-fn have little to do with state, so they probably belonged in core as well, and we found that the name wrap-fn was confusing, hence invoke.

The same conversation also revealed that there is some pain in having to require 4 namespaces in order to use state-flow in its most common use at Nubank: with clojure.test and matcher-combinators. That's what led us to the api namespace.

TODO

  • consider recommending aliasing [state-flow.api :as flow]
    • that would put nice things like flow/invoke and flow/return in flows.
  • update README
  • update walkthrough

@sovelten
Copy link
Collaborator

sovelten commented May 12, 2020

Does it make sense to move this to state-flow.api now? That will probably save another deprecation.

Still looks inconsistent to me, return is part of the primitives.
Of course you could simulate (return v) with (state/gets (constantly v)), but that's not the point.

I would keep return and invoke exactly where they are and create a state-flow.api where we can export return, invoke, modify (or whatever name)

From the library development perspective it makes sense to keep state primitives in the state-flow.state namespace because they are the building blocks on top of which we build state-flow itself. There is a hierarchy of dependency. state-flow.core depends on state-flow.state. From the user perspective it makes sense to have all the user level api in a single namespace. Can we have our cake and eat it too?

@dchelimsky
Copy link
Contributor Author

@sovelten We're definitely interested in all forms of possessing and consuming 🍰!

There's a disconnect between your view of return and the one a few of us on test-pack have developed. You're saying it's part of the primitives, but we see it (and invoke) as fundamentally different from e.g. get and modify as it does not actually interact with state in any way:

That's because get and modify get and modify state, whereas return and invoke have nothing to do with state. If we leave them all in the same namespace, then they don't make as much sense:

flow/get    ;; get what?
flow/modify ;; modify what?
flow/return
flow/invoke

api/get    ;; get what?
api/modify ;; modify what?
api/return
api/invoke

We think this organization is more intention revealing:
nse (to us):

state/get    ;; oh! We're getting state!
state/modify ;; oh! We're getting state!
flow/return
flow/invoke

Is that clear?

@sovelten
Copy link
Collaborator

We think this organization is more intention revealing:
nse (to us):

state/get    ;; oh! We're getting state!
state/modify ;; oh! We're getting state!
flow/return
flow/invoke

Is that clear?

That's quite clear, I'm just not sure I agree.

(state/return v) -> (State. (fn [s] [v s]))
(state/invoke f) -> (State. (fn [s] [(f) s]))

Even though they don't change the state, they deal directly with the state transition machinery and data structure defined in the state ns, one we don't want to expose the rest of the library to.

Let me say first of all that state monad is a terrible name for this monad (naming is still the most difficult thing in computer science (and monad is a terrible name for this kind of abstraction (oh well...))), because the data structure is not a state, it is a state transition function (that may or may not change the state) and that may be the reason for the apparent disconnection of return and invoke. It's not about changing state, it is about composing state aware functions.

That's why I propose to keep return and invoke where they are and create an api in which we can do:

(flow/invoke ...)
(flow/get-state ...) ; names may vary
(flow/swap-state ...)
(flow/return ...)

I believe this is having the cake and eating it.

@dchelimsky
Copy link
Contributor Author

That's why I propose to keep return and invoke where they are and create an api in which we can do:

(flow/invoke ...)
(flow/get-state ...) ; names may vary
(flow/swap-state ...)
(flow/return ...)

I believe this is having the cake and eating it.

That works for me, personally, but @philomates expressed a distaste for fns named get-xxx vs xxx/get. @philomates can you be convinced this is an astoundingly beautiful approach?

@dchelimsky
Copy link
Contributor Author

Let me say first of all that state monad is a terrible name for this monad (naming is still the most difficult thing in computer science (and monad is a terrible name for this kind of abstraction (oh well...))), because the data structure is not a state, it is a state transition function (that may or may not change the state) and that may be the reason for the apparent disconnection of return and invoke. It's not about changing state, it is about composing state aware functions.

I don't see this fully (though I'm trying). What is "state aware" about return and invoke? Are you saying they're state-aware, but just choose to ignore state (making them state-ignorant and state-aware at the same time!)?

@sovelten
Copy link
Collaborator

sovelten commented May 12, 2020

Let me say first of all that state monad is a terrible name for this monad (naming is still the most difficult thing in computer science (and monad is a terrible name for this kind of abstraction (oh well...))), because the data structure is not a state, it is a state transition function (that may or may not change the state) and that may be the reason for the apparent disconnection of return and invoke. It's not about changing state, it is about composing state aware functions.

I don't see this fully (though I'm trying). What is "state aware" about return and invoke? Are you saying they're state-aware, but just choose to ignore state (making them state-ignorant and state-aware at the same time!)?

Yeah... I guess? 😅
Let's call this thing blorb. All that get, put, return, invoke do is create raw blorb. We don't want to deal with blorb directly because blorb is ugly and also it would be an abstraction leak, we want blorb contained, we don't want people ever to know that they can create blorb with (State. (fn [s] ...), not even state-flow.core, because if you don't use the api you might end up with a corrupted/useless blorb. So instead we use those functions as a blorb api to creating and composing blorb.

Now, the blorb api just happens to also be useful for composing flows (because flows deep down are just fancy blorbs). So we can expose part or all of the blorb api to the user. Hope that all makes sense now.

(after note: Some of the things we deal with are not even an api to blorb, they are an api to any wobble, which is a protocol that blorb implements. This is the case for fmap, sequence and possibly others. return is directly part of the implementation of the wobble protocol for blorb)

@dchelimsky
Copy link
Contributor Author

Now, the blorb api just happens to also be useful for composing flows (because flows deep down are just fancy blorbs). So we can expose part or all of the blorb api to the user. Hope that all makes sense now.

Not a bit, but thanks for trying ;)

@dchelimsky
Copy link
Contributor Author

That's why I propose to keep return and invoke where they are and create an api in which we can do:

(flow/invoke ...)
(flow/get-state ...) ; names may vary
(flow/swap-state ...)
(flow/return ...)

I believe this is having the cake and eating it.

@sovelten as ordens!

I think this is great now. Still need to update the README and walkthrough, but we're getting close.

@dchelimsky dchelimsky marked this pull request as draft May 12, 2020 21:16
@dchelimsky dchelimsky changed the title add core/fmap, core/return, and core/invoke (take 2) add state-flow.api with fmap, return, and invoke May 13, 2020
@dchelimsky dchelimsky changed the title add state-flow.api with fmap, return, and invoke add state-flow.api namespace May 13, 2020
@dchelimsky dchelimsky marked this pull request as ready for review May 13, 2020 15:38
@@ -92,7 +100,7 @@
(m/return ret#))))

(defmacro flow
"Defines a flow"
"Creates a flow which is a composite of flows."
Copy link
Contributor

Choose a reason for hiding this comment

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

🤯 Xzibit approves

@dchelimsky dchelimsky merged commit 2f66f75 into master May 18, 2020
@dchelimsky dchelimsky deleted the dc/fmap-take-2 branch May 18, 2020 14:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants