mirror of
https://github.com/qfpl/applied-fp-course.git
synced 2024-11-23 03:44:45 +03:00
Restructured the Types.hs a bit to help with flow
This commit is contained in:
parent
c5264cef02
commit
e491acb26e
@ -17,9 +17,9 @@ import Data.Text.Encoding (decodeUtf8)
|
||||
import FirstApp.Types
|
||||
|
||||
|
||||
-- |------------------------------------------------------|
|
||||
-- | Don't start here, go straight to FirstApp.Types! :) |
|
||||
-- |------------------------------------------------------|
|
||||
-- ---------------------------------------------|
|
||||
-- Don't start here, go to FirstApp.Types! :) |
|
||||
-- ---------------------------------------------|
|
||||
|
||||
runApp :: IO ()
|
||||
runApp = run 3000 app
|
||||
@ -65,6 +65,7 @@ mkRequest rq =
|
||||
-- These helpers will take the raw request information and turn it into
|
||||
-- one of our data types. This means we draw a line about where the unruly outside
|
||||
-- world must end, and where the well-typed world of our application begins.
|
||||
|
||||
mkAddRequest
|
||||
:: Text
|
||||
-> LBS.ByteString
|
||||
@ -75,25 +76,27 @@ mkAddRequest ti c =
|
||||
-- This has other benefits, we're able isolate our validation requirements into the
|
||||
-- smallest chunks we can manage. This allows for fantastic reuse and it also means
|
||||
-- that validation is not spread across the application. It is kept at the borders.
|
||||
|
||||
mkViewRequest
|
||||
:: Text
|
||||
-> Either Error RqType
|
||||
mkViewRequest =
|
||||
error "mkViewRequest not implemented"
|
||||
|
||||
-- Even thought it may seem trivial or even pointless to write functions such as these
|
||||
-- it allows for much greater consistency across the application.
|
||||
|
||||
-- These are straight forward data constructors, but by doing it this way we don't
|
||||
-- have any snowflakes littered about the code. It also enhances our ability to
|
||||
-- spot larger patterns in our application, which are opportunities for abstraction.
|
||||
-- Even though it may seem too trivial or even pointless to write functions such
|
||||
-- as these it allows for much greater consistency across the application.
|
||||
--
|
||||
-- Some of these are straight forward data constructors, but by doing it this
|
||||
-- way we don't have any snowflakes littered about the code. It also enhances
|
||||
-- our ability to spot larger patterns in our application, which are
|
||||
-- opportunities for abstraction.
|
||||
mkListRequest
|
||||
:: Either Error RqType
|
||||
mkListRequest =
|
||||
error "mkListRequest not implemented"
|
||||
|
||||
{-|
|
||||
HALP
|
||||
HALP - wording - is it useful?
|
||||
|
||||
Alternative type sig:
|
||||
Either Error a
|
||||
@ -112,20 +115,17 @@ mkErrorResponse
|
||||
mkErrorResponse _ =
|
||||
error "mkErrorResponse not implemented"
|
||||
|
||||
{-|
|
||||
We'll stub these for now as the general structure and the process of reaching
|
||||
this stage is the more important lesson here.
|
||||
|
||||
Notice how we're only accepting our predefined request types that have the required
|
||||
information already validated and prepared for use in the handling of the request.
|
||||
|
||||
If we find that we need more information to handle a request, or we have a new
|
||||
type of request that we'd like to handle then we simply update the RqType structure
|
||||
and the compiler will let us know the affected portions of our application.
|
||||
|
||||
Reduction of concerns such that each section of the application only deals with
|
||||
a small piece is one of the benefits of developing in this way.
|
||||
-}
|
||||
-- Notice how we're only accepting our predefined request types that have the
|
||||
-- required information already validated and prepared for use in the handling
|
||||
-- of the request.
|
||||
--
|
||||
-- If we find that we need more information to handle a request, or we have a
|
||||
-- new type of request that we'd like to handle then we simply update the RqType
|
||||
-- structure and the compiler will let us know the affected portions of our
|
||||
-- application.
|
||||
--
|
||||
-- Reduction of concerns such that each section of the application only deals
|
||||
-- with a small piece is one of the benefits of developing in this way.
|
||||
handleRequest
|
||||
:: RqType
|
||||
-> Either Error Response
|
||||
|
@ -23,23 +23,33 @@ data RqType
|
||||
-- types reflect when errors can be introduced into our program. Additionally
|
||||
-- it's useful to be able to be descriptive about what went wrong.
|
||||
|
||||
-- So lets think about some of the basic things that can wrong with our
|
||||
-- program and create some values to represent that.
|
||||
-- Think about some of the basic things that can wrong with our Requests and
|
||||
-- building the RqTypes, and create some values to represent that.
|
||||
data Error
|
||||
-- For now we don't need to worry about things like malformed requests or
|
||||
-- invalid headers etc.
|
||||
|
||||
-- Provide a type to list our response content types so we don't try to
|
||||
-- do the wrong thing with what we meant to be used as text/JSON etc.
|
||||
data ContentType
|
||||
--
|
||||
-- The ContentType description for a header doesn't match our data definition
|
||||
-- so we write a little helper function to pattern match on our ContentType
|
||||
-- value and provide the correct header value.
|
||||
renderContentType
|
||||
:: ContentType
|
||||
-> ByteString
|
||||
renderContentType =
|
||||
error "renderContentType not implemented"
|
||||
|
||||
-- In Haskell the `newtype` comes with zero runtime cost. It is purely used for
|
||||
-- typechecking. So when you have a bare 'primitive' value, like an Int, String, or
|
||||
-- even [a], you can wrap it up in a `newtype` for clarity.
|
||||
|
||||
-- The type system will check it for you, and the compiler will eliminate the cost
|
||||
-- once it has passed.
|
||||
-- The type system will check it for you, and the compiler will eliminate the cost.
|
||||
|
||||
-- Having specialised constructor functions for the newtypes allows you to set
|
||||
-- restrictions for your newtype.
|
||||
-- extra restrictions for your newtype.
|
||||
|
||||
-- Write two `newtype` definitions for `Topic` and `CommentText` that wrap a
|
||||
-- `Text` value
|
||||
@ -52,10 +62,9 @@ data ContentType
|
||||
|
||||
-- |
|
||||
|
||||
-- An additional benefit of `newtype` is that we can choose to not export the
|
||||
-- constructor and provide a function of our own. In our case, we're not
|
||||
-- interested in empty `Text` values so we can eliminate them and immediately
|
||||
-- report an error.
|
||||
-- A benefit of `newtype` is that we can choose to *not* export the constructor
|
||||
-- and provide a function of our own. In our case, we're not interested in empty
|
||||
-- `Text` values so we can eliminate them and immediately report an error.
|
||||
mkTopic
|
||||
:: Text
|
||||
-> Either Error Topic
|
||||
@ -69,13 +78,6 @@ mkCommentText =
|
||||
error "mkCommentText not implemented"
|
||||
|
||||
-- After you've implemented these functions, adjust the export list in the
|
||||
-- module declaration so your constructor functions cannot be bypassed.
|
||||
-- module declaration so your constructor functions cannot be bypassed. Also
|
||||
-- export the functions that let you access the inner value of the newtype.
|
||||
|
||||
-- The ContentType description for a header doesn't match our data definition
|
||||
-- so we write a little helper function to pattern match on our ContentType
|
||||
-- value and provide the correct header value.
|
||||
renderContentType
|
||||
:: ContentType
|
||||
-> ByteString
|
||||
renderContentType =
|
||||
error "renderContentType not implemented"
|
||||
|
Loading…
Reference in New Issue
Block a user