1
1
mirror of https://github.com/qfpl/applied-fp-course.git synced 2024-11-25 19:24:08 +03:00

Merge pull request #88 from qfpl/semigroup-last

Last from Monoid is deprecated, use the one from Semigroup
This commit is contained in:
Sean Chalmers 2019-09-19 12:56:56 +10:00 committed by GitHub
commit e0832881cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 57 additions and 78 deletions

View File

@ -2,7 +2,7 @@ module Level06.Conf.CommandLine
( commandLineParser ( commandLineParser
) where ) where
import Data.Monoid (Last (Last), (<>)) import Data.Semigroup (Last (Last), (<>))
import Options.Applicative (Parser, eitherReader, execParser, import Options.Applicative (Parser, eitherReader, execParser,
fullDesc, header, help, helper, info, fullDesc, header, help, helper, info,
@ -11,7 +11,7 @@ import Options.Applicative (Parser, eitherReader, execParser,
import Text.Read (readEither) import Text.Read (readEither)
import Level06.Types (DBFilePath (DBFilePath), import Level06.Types (DBFilePath (DBFilePath),
PartialConf (PartialConf), Port (Port)) PartialConf (PartialConf), Port (Port))
-- | Command Line Parsing -- | Command Line Parsing
@ -36,7 +36,7 @@ partialConfParser =
-- Parse the Port value off the command line args and into a Last wrapper. -- Parse the Port value off the command line args and into a Last wrapper.
portParser portParser
:: Parser (Last Port) :: Parser (Maybe (Last Port))
portParser = portParser =
let let
mods = long "port" mods = long "port"
@ -46,11 +46,11 @@ portParser =
-- A custom parser to turn a String into a Word16, before putting it into a Port -- A custom parser to turn a String into a Word16, before putting it into a Port
portReader = eitherReader (fmap Port . readEither) portReader = eitherReader (fmap Port . readEither)
in in
Last <$> optional (option portReader mods) fmap Last <$> optional (option portReader mods)
-- Parse the DBFilePath from the input string into our type and into a Last wrapper. -- Parse the DBFilePath from the input string into our type and into a Last wrapper.
dbFilePathParser dbFilePathParser
:: Parser (Last DBFilePath) :: Parser (Maybe (Last DBFilePath))
dbFilePathParser = dbFilePathParser =
let let
mods = long "db-filepath" mods = long "db-filepath"
@ -58,4 +58,4 @@ dbFilePathParser =
<> metavar "DBFILEPATH" <> metavar "DBFILEPATH"
<> help "File path for our SQLite Database file." <> help "File path for our SQLite Database file."
in in
Last <$> optional (DBFilePath <$> strOption mods) fmap Last <$> optional (DBFilePath <$> strOption mods)

View File

@ -36,9 +36,7 @@ import Data.Text (Text, pack)
import System.IO.Error (IOError) import System.IO.Error (IOError)
import Data.Monoid (Last (..), import Data.Semigroup (Last (..), Semigroup ((<>)))
Monoid (mappend, mempty))
import Data.Semigroup (Semigroup ((<>)))
import Data.Functor.Contravariant ((>$<)) import Data.Functor.Contravariant ((>$<))
import Data.List (stripPrefix) import Data.List (stripPrefix)
@ -172,8 +170,8 @@ confPortToWai
confPortToWai = confPortToWai =
error "confPortToWai not implemented" error "confPortToWai not implemented"
-- Similar to when we were considering our application types. We can add to this sum type as we -- Similar to when we were considering our application types. We can add to this sum type
-- build our application and the compiler can help us out. -- as we build our application and the compiler can help us out.
data ConfigError data ConfigError
= BadConfFile DecodeError = BadConfFile DecodeError
deriving Show deriving Show
@ -189,40 +187,31 @@ data ConfigError
-- ``defaults <> file <> commandLine`` -- ``defaults <> file <> commandLine``
-- We can use the ``Monoid`` typeclass to handle combining the ``Conf`` records -- We can use the ``Semigroup`` typeclass to handle combining the ``Conf`` records
-- together, and the ``Last`` type to wrap up our values to handle the desired -- together, and the ``Last`` type to wrap up our values to handle the desired
-- precedence. The ``Last`` type is a wrapper for Maybe that when used with its -- precedence. The ``Last`` type is a wrapper for Maybe that when used with its
-- ``Monoid`` instance will always preference the last ``Just`` value that it -- ``Semigroup`` instance will always preference the last value that it has:
-- has: --
-- Just (Last 3) <> Just (Last 1) = Just (Last 1)
-- Last (Just 3) <> Last (Just 1) = Last (Just 1) -- Nothing <> Just (Last 1) = Just (Last 1)
-- Last Nothing <> Last (Just 1) = Last (Just 1) -- Just (Last 1) <> Nothing = Just (Last 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``
-- To make this easier, we'll make a new type ``PartialConf`` that will have our -- wrapped values. We can then define a ``Semigroup`` instance for it and have our
-- ``Last`` wrapped values. We can then define a ``Monoid`` instance for it and -- ``Conf`` be a known good configuration.
-- have our ``Conf`` be a known good configuration.
data PartialConf = PartialConf data PartialConf = PartialConf
{ pcPort :: Last Port { pcPort :: Maybe (Last Port)
, pcDBFilePath :: Last DBFilePath , pcDBFilePath :: Maybe (Last DBFilePath)
} }
-- Before we can define our ``Monoid`` instance for ``PartialConf``, we'll have -- We need to define a ``Semigroup`` instance for ``PartialConf``. We define our ``(<>)``
-- to define a Semigroup instance. We define our ``(<>)`` function to lean -- function to lean on the ``Semigroup`` instance for Last to always get the last value.
-- on the ``Semigroup`` instance for Last to always get the last value.
instance Semigroup PartialConf where instance Semigroup PartialConf where
_a <> _b = PartialConf _a <> _b = PartialConf
{ pcPort = error "pcPort (<>) not implemented" { pcPort = error "pcPort (<>) not implemented"
, pcDBFilePath = error "pcDBFilePath (<>) not implemented" , pcDBFilePath = error "pcDBFilePath (<>) not implemented"
} }
-- We now define our ``Monoid`` instance for ``PartialConf``. Allowing us to
-- define our always empty configuration, which would always fail our
-- requirements. We just define `mappend` to be an alias of ``(<>)``
instance Monoid PartialConf where
mempty = PartialConf mempty mempty
mappend = (<>)
-- | When it comes to reading the configuration options from the command-line, we -- | When it comes to reading the configuration options from the command-line, we
-- use the 'optparse-applicative' package. This part of the exercise has already -- use the 'optparse-applicative' package. This part of the exercise has already
-- been completed for you, feel free to have a look through the 'CommandLine' -- been completed for you, feel free to have a look through the 'CommandLine'

View File

@ -7,7 +7,7 @@ module Level07.Conf
import GHC.Word (Word16) import GHC.Word (Word16)
import Data.Bifunctor (first) import Data.Bifunctor (first)
import Data.Monoid (Last (..), (<>)) import Data.Semigroup (Last (..), (<>))
import Level07.Types (Conf (..), ConfigError (..), import Level07.Types (Conf (..), ConfigError (..),
DBFilePath (DBFilePath), DBFilePath (DBFilePath),
@ -21,8 +21,8 @@ import Level07.Conf.File (parseJSONConfigFile)
defaultConf defaultConf
:: PartialConf :: PartialConf
defaultConf = PartialConf defaultConf = PartialConf
(pure (Port 3000)) (pure (Last $ Port 3000))
(pure (DBFilePath "app_db.db")) (pure (Last $ DBFilePath "app_db.db"))
-- We need something that will take our PartialConf and see if can finally build -- 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 config values -- a complete Conf record. Also we need to highlight any missing config values
@ -39,10 +39,10 @@ makeConfig pc = Conf
-- like to be explicit in your intentions. -- like to be explicit in your intentions.
lastToEither lastToEither
:: ConfigError :: ConfigError
-> (PartialConf -> Last b) -> (PartialConf -> Maybe (Last b))
-> Either ConfigError b -> Either ConfigError b
lastToEither e g = lastToEither e g =
(maybe (Left e) Right . getLast . g) pc maybe (Left e) (Right . getLast) . g $ pc
-- This is the function we'll actually export for building our configuration. -- This is the function we'll actually export for building our configuration.
-- Since it wraps all our efforts to read information from the command line, and -- Since it wraps all our efforts to read information from the command line, and

View File

@ -2,7 +2,7 @@ module Level07.Conf.CommandLine
( commandLineParser ( commandLineParser
) where ) where
import Data.Monoid (Last (Last), (<>)) import Data.Semigroup (Last (Last), (<>))
import Options.Applicative (Parser, eitherReader, execParser, import Options.Applicative (Parser, eitherReader, execParser,
fullDesc, header, help, helper, info, fullDesc, header, help, helper, info,
@ -11,7 +11,7 @@ import Options.Applicative (Parser, eitherReader, execParser,
import Text.Read (readEither) import Text.Read (readEither)
import Level07.Types (DBFilePath (DBFilePath), import Level07.Types (DBFilePath (DBFilePath),
PartialConf (PartialConf), Port (Port)) PartialConf (PartialConf), Port (Port))
-- | Command Line Parsing -- | Command Line Parsing
@ -36,7 +36,7 @@ partialConfParser =
-- Parse the Port value off the command line args and into a Last wrapper. -- Parse the Port value off the command line args and into a Last wrapper.
portParser portParser
:: Parser (Last Port) :: Parser (Maybe (Last Port))
portParser = portParser =
let let
mods = long "port" mods = long "port"
@ -46,11 +46,11 @@ portParser =
-- A custom parser to turn a String into a Word16, before putting it into a Port -- A custom parser to turn a String into a Word16, before putting it into a Port
portReader = eitherReader (fmap Port . readEither) portReader = eitherReader (fmap Port . readEither)
in in
Last <$> optional (option portReader mods) fmap Last <$> optional (option portReader mods)
-- Parse the DBFilePath from the input string into our type and into a Last wrapper. -- Parse the DBFilePath from the input string into our type and into a Last wrapper.
dbFilePathParser dbFilePathParser
:: Parser (Last DBFilePath) :: Parser (Maybe (Last DBFilePath))
dbFilePathParser = dbFilePathParser =
let let
mods = long "db-filepath" mods = long "db-filepath"
@ -58,4 +58,4 @@ dbFilePathParser =
<> metavar "DBFILEPATH" <> metavar "DBFILEPATH"
<> help "File path for our SQLite Database file." <> help "File path for our SQLite Database file."
in in
Last <$> optional (DBFilePath <$> strOption mods) fmap Last <$> optional (DBFilePath <$> strOption mods)

View File

@ -32,8 +32,7 @@ import Data.ByteString (ByteString)
import Data.Text (pack) import Data.Text (pack)
import Data.Functor.Contravariant ((>$<)) import Data.Functor.Contravariant ((>$<))
import Data.Monoid (Last (Last)) import Data.Semigroup (Last (Last), Semigroup ((<>)))
import Data.Semigroup (Semigroup ((<>)))
import Data.Time (UTCTime) import Data.Time (UTCTime)
import qualified Data.Time.Format as TF import qualified Data.Time.Format as TF
@ -162,51 +161,42 @@ data ConfigError
| ConfigFileReadError IOError | ConfigFileReadError IOError
deriving Show deriving Show
-- Our application will be able to load configuration from both a file and -- Our application will be able to load configuration from both a file and command line
-- command line input. We want to be able to use the command line to temporarily -- input. We want to be able to use the command line to temporarily override the
-- override the configuration from our file. How do we combine the different -- configuration from our file. How do we combine the different inputs to enable this
-- inputs to enable this property? -- property?
-- We want the command line configuration to take precedence over the File -- We want the command line configuration to take precedence over the File configuration,
-- configuration, so if we think about combining each of our ``Conf`` records, -- so if we think about combining each of our ``Conf`` records, we want to be able to
-- we want to be able to write something like this: -- write something like this:
-- ``defaults <> file <> commandLine`` -- ``defaults <> file <> commandLine``
-- We can use the ``Monoid`` typeclass to handle combining the ``Conf`` records -- We can use the ``Semigroup`` typeclass to handle combining the ``Conf`` records
-- together, and the ``Last`` type to wrap up our values to handle the desired -- together, and the ``Last`` type to wrap up our values to handle the desired
-- precedence. The ``Last`` type is a wrapper for Maybe that when used with its -- precedence. The ``Last`` type is a wrapper for Maybe that when used with its
-- ``Monoid`` instance will always preference the last ``Just`` value that it -- ``Semigroup`` instance will always preference the last value that it has:
-- has: --
-- Just (Last 3) <> Just (Last 1) = Just (Last 1)
-- Last (Just 3) <> Last (Just 1) = Last (Just 1) -- Nothing <> Just (Last 1) = Just (Last 1)
-- Last Nothing <> Last (Just 1) = Last (Just 1) -- Just (Last 1) <> Nothing = Just (Last 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``
-- To make this easier, we'll make a new type ``PartialConf`` that will have our -- wrapped values. We can then define a ``Semigroup`` instance for it and have our
-- ``Last`` wrapped values. We can then define a ``Monoid`` instance for it and -- ``Conf`` be a known good configuration.
-- have our ``Conf`` be a known good configuration.
data PartialConf = PartialConf data PartialConf = PartialConf
{ pcPort :: Last Port { pcPort :: Maybe (Last Port)
, pcDBFilePath :: Last DBFilePath , pcDBFilePath :: Maybe (Last DBFilePath)
} }
-- Before we can define our ``Monoid`` instance for ``PartialConf``, we'll have -- We need to define a ``Semigroup`` instance for ``PartialConf``. We define our ``(<>)``
-- to define a Semigroup instance. We define our ``(<>)`` function to lean -- function to lean on the ``Semigroup`` instance for Last to always get the last value.
-- on the ``Semigroup`` instance for Last to always get the last value.
instance Semigroup PartialConf where instance Semigroup PartialConf where
a <> b = PartialConf a <> b = PartialConf
{ pcPort = pcPort a <> pcPort b { pcPort = pcPort a <> pcPort b
, pcDBFilePath = pcDBFilePath a <> pcDBFilePath b , pcDBFilePath = pcDBFilePath a <> pcDBFilePath b
} }
-- We now define our ``Monoid`` instance for ``PartialConf``. Allowing us to
-- define our always empty configuration, which would always fail our
-- requirements. We just define `mappend` to be an alias of ``(<>)``
instance Monoid PartialConf where
mempty = PartialConf mempty mempty
mappend = (<>)
-- When it comes to reading the configuration options from the command-line, we -- When it comes to reading the configuration options from the command-line, we
-- use the 'optparse-applicative' package. This part of the exercise has already -- use the 'optparse-applicative' package. This part of the exercise has already
-- been completed for you, feel free to have a look through the 'CommandLine' -- been completed for you, feel free to have a look through the 'CommandLine'
@ -221,7 +211,7 @@ partialConfDecoder = PartialConf
<$> lastAt "port" D.integral Port <$> lastAt "port" D.integral Port
<*> lastAt "dbFilePath" D.string DBFilePath <*> lastAt "dbFilePath" D.string DBFilePath
where where
lastAt k d c = Last . fmap c <$> D.atKeyOptional k d lastAt k d c = fmap (Last . c) <$> D.atKeyOptional k d
-- We have a data type to simplify passing around the information we need to run -- We have a data type to simplify passing around the information we need to run
-- our database queries. This also allows things to change over time without -- our database queries. This also allows things to change over time without