1
1
mirror of https://github.com/qfpl/applied-fp-course.git synced 2024-11-23 03:44:45 +03:00

Wording/formatting updates

This commit is contained in:
Sean Chalmers 2017-09-05 16:13:38 +10:00
parent 3a8a44be9f
commit dc42b2091f
10 changed files with 77 additions and 81 deletions

View File

@ -38,14 +38,13 @@ newtype HelloMsg = HelloMsg
{ getHelloMsg :: ByteString }
deriving (Eq, Show)
-- The Conf type will need:
-- The ``Conf`` type will need:
-- - A customisable port number: `Port`
-- - A changeable message for our users: `HelloMsg`
data Conf = Conf
-- Similar to when we were considering what might go wrong with the RqType,
-- lets think about might go wrong when trying to gather our configuration
-- information.
-- Similar to when we were considering what might go wrong with the RqType, lets
-- think about might go wrong when trying to gather our config information.
data ConfigError
-- Our application will be able to load configuration from both a file and
@ -54,29 +53,29 @@ data ConfigError
-- question will help us find which abstraction is correct for our needs...
-- We want the CommandLine configuration to take precedence over the File
-- configuration, so if we think about combining each of our Conf records, we want
-- to be able to write something like this:
-- configuration, so if we think about combining each of our ``Conf`` records,
-- we want to be able to write something like this:
-- defaults <> file <> commandLine
-- We can use the Monoid typeclass to handle combining the Conf records
-- together, and the Last newtype to wrap up our values to handle the desired precedence.
-- The Last newtype is a wrapper for Maybe that when used with its Monoid instance
-- will always preference the last Just value that it has:
-- We can use the ``Monoid`` typeclass to handle combining the ``Conf`` records
-- together, and the Last newtype to wrap up our values to handle the desired
-- precedence. The Last newtype is a wrapper for Maybe that when used with its
-- ``Monoid`` instance will always preference the last Just value that it has:
-- Last (Just 3) <> Last (Just 1) = Last (Just 1)
-- Last Nothing <> Last (Just 1) = Last (Just 1)
-- Last (Just 1) <> Last Nothing = Last (Just 1)
-- To make this easier, we'll make a new type `PartialConf` that will have our Last
-- wrapped values. We can then define a Monoid instance for it and have our Conf be
-- a known good configuration.
-- To make this easier, we'll make a new type `PartialConf` that will have our
-- Last wrapped values. We can then define a ``Monoid`` instance for it and have
-- our ``Conf`` be a known good configuration.
data PartialConf
-- We now define our Monoid instance for PartialConf. Allowing us to define our
-- always empty configuration, which would always fail our requirements. More
-- interestingly, we define our mappend function to lean on the Monoid instance
-- for Last to always get the last value.
-- We now define our ``Monoid`` instance for PartialConf. Allowing us to define
-- our always empty configuration, which would always fail our requirements.
-- More interestingly, we define our ``mappend`` function to lean on the
-- ``Monoid`` instance for Last to always get the last value.
instance Monoid PartialConf where
-- Set some sane defaults that we can always rely on
@ -86,7 +85,7 @@ defaultConf =
error "defaultConf not implemented"
-- We need something that will take our PartialConf and see if can finally build
-- a complete Conf record. Also we need to highlight any missing values by
-- a complete ``Conf`` record. Also we need to highlight any missing values by
-- providing the relevant error.
makeConfig
:: PartialConf
@ -105,11 +104,11 @@ parseOptions =
-- | File Parsing
-- We're trying to avoid complications when selecting a configuration file package
-- from Hackage. We'll use an encoding that you are probably familiar with, for
-- better or worse, and write a small parser to pull out the bits we need. The package
-- we're using is the 'aeson' package to parse some JSON and we'll pick the bits off
-- the Object.
-- We're trying to avoid complications when selecting a configuration file
-- package from Hackage. We'll use an encoding that you are probably familiar
-- with, for better or worse, and write a small parser to pull out the bits we
-- need. The package we're using is the ``aeson`` package to parse some JSON and
-- we'll pick the bits off the Object.
-- The documentation for this package will guide you in the right direction
parseJSONConfigFile
@ -120,13 +119,13 @@ parseJSONConfigFile =
-- | Command Line Parsing
-- We will use the 'optparse-applicative' package to build our command line parser.
-- As this particular problem is fraught with silly dangers and we appreciate
-- someone else having eaten this gremlin on our behalf.
-- We will use the ``optparse-applicative`` package to build our command line
-- parser. As this particular problem is fraught with silly dangers and we
-- appreciate someone else having eaten this gremlin on our behalf.
-- You'll need to use the documentation for optparse-applicative to help you write
-- these functions as we're relying on their API to produce the types we need. We've
-- provided some of the less interesting boilerplate for you.
-- You'll need to use the documentation for ``optparse-applicative`` to help you
-- write these functions as we're relying on their API to produce the types we
-- need. We've provided some of the less interesting boilerplate for you.
commandLineParser
:: ParserInfo PartialConf
commandLineParser =

View File

@ -72,8 +72,8 @@ app cfg rq cb =
handleRErr =
either Left ( handleRequest cfg )
-- Now we have some config, we can pull our configured helloMsg off it and use it
-- in the response.
-- Now we have some config, we can pull the helloMsg off it and use it in the
-- response.
handleRequest
:: a
-> RqType
@ -99,7 +99,7 @@ mkRequest rq =
-- List the current topics
( ["list"], "GET" ) ->
pure mkListRequest
-- Finally we don't care about any other requests so throw your hands in the air
-- Finally we don't care about any other requests so build an Error response
_ ->
pure mkUnknownRouteErr

View File

@ -76,9 +76,9 @@ data ContentType
= PlainText
| JSON
-- 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.
-- 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

View File

@ -145,7 +145,7 @@ parseJSONConfigFile fp = do
-- | Command Line Parsing
-- We will use the optparse-applicative package to build our command line
-- We will use the ``optparse-applicative`` package to build our command line
-- parser, as this problem is fraught with silly dangers and we appreciate
-- someone else having eaten this gremlin on our behalf.
commandLineParser

View File

@ -15,14 +15,12 @@ module FirstApp.Types
import Data.ByteString (ByteString)
import Data.Text (Text)
{-|
In Haskell the `newtype` comes with zero runtime cost. It is purely used for
type-checking. 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.
-- In Haskell the `newtype` comes with zero runtime cost. It is purely used for
-- type-checking. 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 once it has passed.
newtype Topic = Topic Text
deriving Show
@ -68,32 +66,29 @@ data RqType
| ViewRq Topic
| ListRq
{-|
Not everything goes according to plan, but it's important that our
types reflect when errors can be introduced into our program. Additionally
it's useful to be able to be descriptive about what went wrong.
-- Not everything goes according to plan, but it's important that our 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.
-}
-- So lets think about some of the basic things that can wrong with our program
-- and create some values to represent that.
data Error
= UnknownRoute
| EmptyCommentText
| EmptyTopic
deriving Show
-- 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.
-- 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 or JSON.
data ContentType
= PlainText
| JSON
-- 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.
-- 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"
renderContentType PlainText = "text/plain"
renderContentType JSON = "application/json"

View File

@ -62,7 +62,8 @@ withTable =
error "withTable not yet implemented"
-- Given a `FilePath` to our SQLite DB file, initialise the database and ensure
-- our Table is there by running a query to create it, if it doesn't exist already.
-- our Table is there by running a query to create it, if it doesn't exist
-- already.
initDb
:: FilePath
-> Table
@ -76,9 +77,10 @@ initDb fp tab =
createTableQ = withTable tab
"CREATE TABLE IF NOT EXISTS $$tablename$$ (id INTEGER PRIMARY KEY, topic TEXT, comment TEXT, time INTEGER)"
-- Note that we don't store the Comment type in the DB, it is the type we build to
-- send to the outside world. We will be loading our `DbComment` type from the
-- FirstApp.DB.Types module before converting trying to convert it to a Comment.
-- Note that we don't store the Comment type in the DB, it is the type we build
-- to send to the outside world. We will be loading our `DbComment` type from
-- the FirstApp.DB.Types module before converting trying to convert it to a
-- Comment.
getComments
:: FirstAppDB
-> Topic

View File

@ -37,10 +37,10 @@ module FirstApp.DB.PostgreSQL where
-- , dbTable :: Table
-- }
-- -- Unlike the sqlite-simple package, the postgresql-simple package allows for
-- -- the specification of SQL identifiers for us in the Query construction. This
-- -- has the advantage of not needing to worry about manually interpolating the
-- -- table name into our queries. The package will safely handle this for us.
-- Unlike the sqlite-simple package, the postgresql-simple package allows for
-- the specification of SQL identifiers for us in the Query construction. This
-- has the advantage of not needing to worry about manually interpolating the
-- table name into our queries. The package will safely handle this for us.
-- tableName
-- :: FirstAppDB
-- -> Identifier

View File

@ -39,11 +39,11 @@ newtype CommentText = CommentText Text
deriving (Show, ToJSON)
-- This is the Comment record that we will be sending to users, it's a simple
-- record type, containing an Int, Topic, CommentText, and UTCTime. However notice
-- that we've also derived the Generic type class instance as well. This saves us
-- some effort when it comes to creating encoding/decoding instances. Since our
-- types are all simple types at the end of the day, we're able to let GHC do
-- the work.
-- record type, containing an Int, Topic, CommentText, and UTCTime. However
-- notice that we've also derived the Generic type class instance as well. This
-- saves us some effort when it comes to creating encoding/decoding instances.
-- Since our types are all simple types at the end of the day, we're able to let
-- GHC do the work.
newtype CommentId = CommentId Int
deriving (Eq, Show, ToJSON)
@ -57,10 +57,10 @@ data Comment = Comment
deriving ( Show, Generic )
instance ToJSON Comment where
-- This is one place where we can take advantage of our Generic instance. Aeson
-- already has the encoding functions written for anything that implements
-- the Generic typeclass. So we don't have to write our encoding, we ask
-- Aeson to construct it for us.
-- This is one place where we can take advantage of our Generic instance.
-- Aeson already has the encoding functions written for anything that
-- implements the Generic typeclass. So we don't have to write our encoding,
-- we ask Aeson to construct it for us.
toEncoding = A.genericToEncoding opts
where
-- These options let us make some minor adjustments to how Aeson treats
@ -138,9 +138,9 @@ data ContentType
= PlainText
| JSON
-- 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.
-- 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

View File

@ -29,7 +29,8 @@ data Env = Env
}
-- Lets crack on and define a newtype wrapper for our ReaderT, this will save us
-- having to write out the full ReaderT definition for every function that uses it.
-- having to write out the full ReaderT definition for every function that uses
-- it.
newtype AppM a = AppM
-- Our ReaderT will only contain the Env, and our base monad will be IO, leave
-- the return type polymorphic so that it will work regardless of what is
@ -38,12 +39,12 @@ newtype AppM a = AppM
-- different ReaderT when we meant to use our own, or vice versa. In such a
-- situation it is extremely unlikely the application would compile at all,
-- but the name differences alone make the confusion less likely.
--
-- Because we're using a newtype, all of the instance definitions for ReaderT
-- would normally not apply. However, because we've done nothing but create a
-- convenience wrapper for our ReaderT, it is not difficult for GHC to
-- automatically derive instances on our behalf.
--
-- With the 'GeneralizedNewtypeDeriving' pragma at the top of the file, we
-- will be able to derive these instances automatically.
{ unAppM :: ReaderT Env IO a }

View File

@ -48,8 +48,7 @@ closeDb =
-- attempts to mitigate that somewhat by removing the need for repetitive string
-- mangling when building our queries. We write the query and pass it through
-- this function that requires the Table information and everything is taken
-- care of for us. This is probably not the way to do things in a large scale
-- app.
-- care of for us. This is not the way to do things in a large scale program.
withTable
:: Table
-> Query