qfpl / applied-fp-course

Applied Functional Programming Course - Move from exercises to a working app!

Home Page:http://qfpl.io/projects/professional-fp-courses/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Course Feedback

ericdwhite opened this issue · comments

A couple of points I found more challenging then I think they aught to be... This of course varies very much from person to person. And who they have around to answer questions.

A this was self study here are some points I think were harder to grasp.

  1. An overview of the flow of type transformations that code will progress to finally.

It feels like the code is moving towards a pattern where the business logic is clean sperated from HTTP and DB layers. E.g. what is described well in: https://pragprog.com/book/swdddf/domain-modeling-made-functional

I think having some overview in the top level README of what the final process would with some tips:

Wai Request -(a)-> RqType -(b)-> Business Logic -> Event -> DB Dto -> DB -> Business Logic -> JSON Dto -> Response

Tips:
a. The goal of Core.hs mkRequest
b. The goal of Core.hs handleRequest

The rest of the chain is still not clear to me at the moment as I'm still progressing through that :)

  1. There is a big leap from Level01 to Level02 in what you need to do in App

After working through this and then looking at what is in the repository for Level04 I landed here:

app :: Application
app req respondWith = do
  rqt <- mkRequest req
  resp <- hResponseErr <$> hRqTypeErr rqt
  respondWith resp
  where
    hRqTypeErr :: Either Error RqType -> IO (Either Error Response)
    hRqTypeErr = either (pure . Left) handleRequest

    hResponseErr :: Either Error Response -> Response
    hResponseErr = either mkErrorResponse id

NOTE: I had changed the handleRequest to include the IO monad (so the above is slightly different than what you would do if you just did level 02 without doing that).

I think resp <- hResponseErr <$> hRqTypeErr rqt is hard to come up with as a begineer from scratch. My first solution wasn't that nice...

However if the helpers hRqTypeErr and hResponseErr had been defined or at least stubbed as methods, I might have got there sooner. Maybe the struggle was worth it ;)

I think what took me time to understand is that the handling of Error happens at two different levels.

  1. handleRequest should be in the IO Monad in Level02

Because you know this is going to be required at some point, I reworked the code in Level02 to include it there.

E.g. handleRequest :: RqType -> IO (Either Error Response)

  1. I think the overloading of the word Request was initially confusing: e.g. Wai Request vs RqType.

It might have been more easily understood if RqType had constructors AddCommand, ListQuery, ViewTopicQuery. Or some other names to make the distinction clearer.

BUT OVERALL I HAVE FOUND THIS FUN AND INTERESTING!

  1. Starting the repl

It took me a while to figure out that best way to get into the repl was

$ cabal new-repl applied-fp-course
and not
$ cabal new-repl level02-exe as :r doesn't reload changes.

Alrighty, I've interleaved my answers with your points. Hopefully this helps!

It feels like the code is moving towards a pattern where the business logic is
clean sperated from HTTP and DB layers. E.g. what is described well in:
https://pragprog.com/book/swdddf/domain-modeling-made-functional

I think having some overview in the top level README of what the final process
would with some tips:

Wai Request -(a)-> RqType -(b)-> Business Logic -> Event -> DB Dto -> DB -> Business Logic -> JSON Dto -> Response

Tips:
a. The goal of Core.hs mkRequest
b. The goal of Core.hs handleRequest

I think more of this comes through in the classroom discussions regarding 'type
driven development'. I'll try for an abridged version below:

The intent is to use the types to define the API such that you narrow your focus
to only what is of interest. So by defining those RqType constructors, you
take the entire universe of possiblities of an incoming Request and discard
everything that does not meet your requirements.

Thus the internals of your application need only care about something that is a
RqType. So you can crack on with solving the important problems rather than
constantly checking "is this still the right request", "is this non-empty", etc
ad infinitum.

It's a change in perspective...

The 'goal' of a is not the implementation of mkRequest, rather it is the
awareness of the benefits of defining RqType. A consequence of that is
mkRequest becomes almost trivial to implement, because you can only produce
types that match your API requirements.

Following on from that, the 'goal' of b is not the implementation of
handleRequest, but understanding that because we've used the types to make our
requirements concrete, we have only three inputs to concern ourselves with. And
we know they are well formed because our efforts previously with the newtypes
and constructor functions have guaranteed that we are in this state.

Consequently the implementations of mkRequest and handleRequest become an
exercise in applying the correct abstractions, rather than having much to do
with business logic. We've locked in our business requirements in the types, our
implementations now have to satisfy those types.

Does that make sense?

Thinking hard upfront and finding/designing types that represent your
requirements can save you a staggering amount of development time. Not to
mention the awesome benefit that appears later when you have to make changes
to existing code.

If I have a chance I'll check that book out and see if it has more formal
defintions of this sort of thinking.

The rest of the chain is still not clear to me at the moment as I'm still
progressing through that :)

Keep going!! Ask for help here or irc/discord/email. :)

There is a big leap from Level01 to Level02 in what you need to do in App

There isn't always a consistent step in effort between the levels, and this
varies from person to person. But this jump happens because level01 is a test to
ensure that your environment is all setup and ready, and so you can familiarise
yourself with how the course is laid out.

I think resp <- hResponseErr <$> hRqTypeErr rqt is hard to come up with as a
begineer from scratch. My first solution wasn't that nice...

I think it's important to note that the solutions that exist in later levels
are not to be taken as: "What you should have written.". A far more useful
approach in the event of a discrepancy that you find jarring is to try to
understand the differences.

The types and abstractions are tools to be used to achieve a goal, this is not
an exam and you should gauge your success on your correctness and ability to
understand what is going on. Do this over comparing for "elegance" or other
subjective things, seek to understand the why.

However if the helpers hRqTypeErr and hResponseErr had been defined or at least
stubbed as methods, I might have got there sooner. Maybe the struggle was worth
it ;)

The struggle is worth it, and remember that a lot of other people have the
benefit of instructors and colleagues when undertaking this course. Self-study
is possible, but it is 'hard-mode' a bit. :)

I think what took me time to understand is that the handling of Error happens at
two different levels.

Yes, this is a key thing to notice.

I think the overloading of the word Request was initially confusing: e.g. Wai Request vs RqType.

It might have been more easily understood if RqType had constructors AddCommand,
ListQuery, ViewTopicQuery. Or some other names to make the distinction clearer.

Ahh naming things, the eternal struggle continues. There is nothing in the
course material that prevents you from renaming things to suit your needs. A
quick whack with the sed hammer or <insert your favourite mass refactoring
tool> could fix that. :)

BUT OVERALL I HAVE FOUND THIS FUN AND INTERESTING!

WOO! That is the primary goal, learning happens as a consequence. :)