pvillega / free-monad-sample

Trying to understand Free Monad by implementing one
43 stars 8 forks source link

questions about the blog post #2

Closed fommil closed 6 years ago

fommil commented 7 years ago

Hi, thank you for this blog post! http://perevillega.com/understanding-free-monads

I have a few questions about how to implement additional functionality:

  1. everything is stateless, how would one model accessing to some resource (e.g. a banking/risk system that can say "yes" or "no" to authorise a buy/sell) where these rules are actually business logic.
  2. what if I want to allow buying of shares in "Extending the language" to happen in parallel?
  3. is there a way to reduce the boilerplate, there seems more boilerplate than code. It seems there is a huge opportunity for tooling (language features, compiler plugins) to automate the creation and implementation of the Free Monad pattern.
  4. what if the auditing system was implemented as a different interpreter? There seems to be a design choice here about when something is considered part of the DSL and when it is part of the interpreter, is there a better rule here?
  5. I assume the network based interpreter is left as an exercise for the reader... but how does one do things like retry with backoff, and so on?
  6. what would a unit test of the business logic look like?
  7. what would a unit (or perhaps functional/integration) test of an interpreter look like?

I have many more questions... but I will leave it there...

edmundnoble commented 7 years ago

First off: I recommend using an IO-type instead of Id as an interpretation target.

  1. Several ways: a) Make your buy method return a boolean indicating success (in-DSL solution) b) Make your target Monad include a notion of failure (in-interpreter solution)

As you have observed, there is often a choice to be made whether or not to include "things" on either side of the interpreter or DSL. In this sense, you choose to either specialize your DSL somewhat for a single interpreter or keep it ignorant of what that interpreter needs. It's a question of taste. In a real application the interpreters and DSLs must evolve concurrently and with attention paid to each others' needs.

  1. Parallelism is expressible with free applicatives. If a Free Monad is a chain of flatMap calls over F[_], a Free Applicative is a chain of ap calls over F[_]. Because ap does not require sequential dependence between operations, they can be performed in parallel.

However, if you want to combine Free monads with free applicatives, so that some of your operations (the ones that you ap together because they don't depend on each other) get performed in parallel, replace the F[_] in your free monad with FreeAp[F, ?]. Then, your free monad is a chain of flatMap calls over FreeAp[F, ?], so every F[_] in a single FreeAp ap chain can be performed in parallel.

I admit this is incredibly annoying, so another approach is to build the FreeAp structure into the Free Monad itself. Eff provides an example of this approach; you can pass it an applicative for parallel calls and a monad for sequential calls at interpret-time and it'll do it all for free, automagically :)

  1. Yes, there are several annotations people have come up with to reduce this boilerplate. @adelbertc came up with the tfm (tagless final macro) project, I've seen a @free before as well as @drdozer's commandeer project.

  2. The auditing system does look to be implemented as a different interpreter; by which I mean it is its own natural transformation, auditPrinter.

  3. Retry with backoff can be handled similarly: put some state in your interpreter.

  4. Unit tests of the business logic typically look like you feeding a "mock" interpreter which does not perform side effects, but just returns values and operates purely on state by modifying and returning new copies of it. In this way instead of depending on the exact structure of the calls made in the business logic (like if you used mocks with mockito, for e.g.) you depend on the way the logic conceptually manipulates values.

  5. Tests of an interpreter typically look like you feeding "mock" programs to it to make sure it works the way you think. If the interpreter performs side effects, it's an integration or functional test. Otherwise it's a unit test.

Don't be afraid to ask more. Sorry if I pre-empted you @pvillega; great post :)

fommil commented 7 years ago

Thanks for responding!

I don't understand your answers to 1 and 2. Would it be much effort for to sketch out what you mean? I don't know what IO Monad is, despite trying to learn about it several times. I also don't really know what the free applicative is, also even after reading posts watching videos.

The honest truth is that most of the docs describing this stuff is actually pretty awful, which is why this blog really stood out for me. It introduced all concepts without cyclic references to things (other blogs and even cats docs are awful at this) or referring to Haskell concepts (which, despite learning and forgetting Haskell twice, is an instant "give up reading" for me)

fommil commented 7 years ago

Further to 1: what if the state is not just managed by this app but is an external system. Consider a central bank/regulator that non-referentially-transparently authorises buys.

And what about streaming? A buy/sell app is a good example, it could be constantly be reacting to incoming market data changes (and may require debouncing/backpressure).

(I'm using these examples speficically because they have a direct analogy in ensime, and are common themes in middle tier applications)

peterneyens commented 7 years ago

For #2 (Free and FreeApplicative combination): there are some links in https://github.com/typelevel/cats/issues/983 that might be helpful.

diesalbla commented 7 years ago

@fommil

The operations (e.g. the instances of trait Orders[A]) are immutable and stateless objects, like in the Command pattern, that contain the information about the operation, but do not execute it. Lifting them into Free, or composing using the monadic methods of Free, is just building an immutable object that contains all commands that may be performed, and describes the control and data dependencies between these operations. However, creating this object does not run any operation or interacts with any external service. Only the interpretation does that.

On regards to your latest question, about interacting with an external service, here is an example, from an application that we recently released, in which we follow the à la carte design. As an example, we have:

I hope this will help.

fommil commented 7 years ago

thanks @peterneyens I've already read/seen many of these references but they don't help me so much.

thanks @diesalbla ... argh, unfortunately scalaz from what you say. I am struggling to understand just the cats variant of how to code like this, I'm afraid this extra mental barrier is too high for me. I'll add this to the list of projects to read. I have been very keen to see a full app using FP like this and perhaps this is the one.

pvillega commented 7 years ago

Hi @fommil,

apologies about the delay, unexpected workload :)

Fortunately I think @edmundnoble and @peterneyens answered everything (Thanks guys!), and @diesalbla provided a great example of integrating authorisation and business logic via the dsl (Gracias! :).

So, not sure what to add, I feel their answers are superior to the ones I'd been able to give. Let me know if I can add something, I'll do my best!

pvillega commented 7 years ago

to add to your last comment @fommil (I should refresh pages) I would also like to see a full FP app, so maybe we have a project for next hack the tower, go over the examples @diesalbla gave us? :)

fommil commented 7 years ago

Definitely. I still have a lot of questions unanswered and practical experience will reveal more. Ideally I'd like to rewrite parts of ensime this way... and perhaps there is a blog post series here of practical FP as the app gets more complex? I'd definitely like more practical-focused docs.

michalrus commented 7 years ago

@fommil, OMG, does this mean that ENSIME will stop underlining cats/scalaz-style code with red in the near future? :tada: :heart_eyes_cat: This would be so exciting!

fommil commented 7 years ago

@michalrus in my code today, ENSIME was fine with an algebra and IntelliJ was choking. As I have said numerous times, to many cats users and friends... if you don't create a test project that demonstrates the problem, I can't help you. I even created https://github.com/ensime/pcplod to make it easy for everybody... but yet nobody has taken me up on the offer.

michalrus commented 7 years ago

Wow, cool! I’ll try to minimize something soon and post some issues! :crossed_swords:

fommil commented 7 years ago

@michaelrus have a read of the Emacs troubleshooting guide, there are some thoughts there about red squigglies and also the main contrib page.