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:
commit
e0832881cf
@ -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)
|
||||||
|
@ -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'
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user