mirror of
https://github.com/qfpl/applied-fp-course.git
synced 2024-11-22 11:23:01 +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
|
||||
) where
|
||||
|
||||
import Data.Monoid (Last (Last), (<>))
|
||||
import Data.Semigroup (Last (Last), (<>))
|
||||
|
||||
import Options.Applicative (Parser, eitherReader, execParser,
|
||||
fullDesc, header, help, helper, info,
|
||||
@ -11,7 +11,7 @@ import Options.Applicative (Parser, eitherReader, execParser,
|
||||
|
||||
import Text.Read (readEither)
|
||||
|
||||
import Level06.Types (DBFilePath (DBFilePath),
|
||||
import Level06.Types (DBFilePath (DBFilePath),
|
||||
PartialConf (PartialConf), Port (Port))
|
||||
|
||||
-- | Command Line Parsing
|
||||
@ -36,7 +36,7 @@ partialConfParser =
|
||||
|
||||
-- Parse the Port value off the command line args and into a Last wrapper.
|
||||
portParser
|
||||
:: Parser (Last Port)
|
||||
:: Parser (Maybe (Last Port))
|
||||
portParser =
|
||||
let
|
||||
mods = long "port"
|
||||
@ -46,11 +46,11 @@ portParser =
|
||||
-- A custom parser to turn a String into a Word16, before putting it into a Port
|
||||
portReader = eitherReader (fmap Port . readEither)
|
||||
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.
|
||||
dbFilePathParser
|
||||
:: Parser (Last DBFilePath)
|
||||
:: Parser (Maybe (Last DBFilePath))
|
||||
dbFilePathParser =
|
||||
let
|
||||
mods = long "db-filepath"
|
||||
@ -58,4 +58,4 @@ dbFilePathParser =
|
||||
<> metavar "DBFILEPATH"
|
||||
<> help "File path for our SQLite Database file."
|
||||
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 Data.Monoid (Last (..),
|
||||
Monoid (mappend, mempty))
|
||||
import Data.Semigroup (Semigroup ((<>)))
|
||||
import Data.Semigroup (Last (..), Semigroup ((<>)))
|
||||
|
||||
import Data.Functor.Contravariant ((>$<))
|
||||
import Data.List (stripPrefix)
|
||||
@ -172,8 +170,8 @@ confPortToWai
|
||||
confPortToWai =
|
||||
error "confPortToWai not implemented"
|
||||
|
||||
-- Similar to when we were considering our application types. We can add to this sum type as we
|
||||
-- build our application and the compiler can help us out.
|
||||
-- Similar to when we were considering our application types. We can add to this sum type
|
||||
-- as we build our application and the compiler can help us out.
|
||||
data ConfigError
|
||||
= BadConfFile DecodeError
|
||||
deriving Show
|
||||
@ -189,40 +187,31 @@ data ConfigError
|
||||
|
||||
-- ``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
|
||||
-- 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
|
||||
-- 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.
|
||||
-- ``Semigroup`` instance will always preference the last value that it has:
|
||||
--
|
||||
-- Just (Last 3) <> Just (Last 1) = Just (Last 1)
|
||||
-- Nothing <> Just (Last 1) = Just (Last 1)
|
||||
-- Just (Last 1) <> Nothing = Just (Last 1)
|
||||
--
|
||||
-- To make this easier, we'll make a new type ``PartialConf`` that will have our ``Last``
|
||||
-- wrapped values. We can then define a ``Semigroup`` instance for it and have our
|
||||
-- ``Conf`` be a known good configuration.
|
||||
data PartialConf = PartialConf
|
||||
{ pcPort :: Last Port
|
||||
, pcDBFilePath :: Last DBFilePath
|
||||
{ pcPort :: Maybe (Last Port)
|
||||
, pcDBFilePath :: Maybe (Last DBFilePath)
|
||||
}
|
||||
|
||||
-- Before we can define our ``Monoid`` instance for ``PartialConf``, we'll have
|
||||
-- to define a Semigroup instance. We define our ``(<>)`` function to lean
|
||||
-- on the ``Semigroup`` instance for Last to always get the last value.
|
||||
-- We need to define a ``Semigroup`` instance for ``PartialConf``. We define our ``(<>)``
|
||||
-- function to lean on the ``Semigroup`` instance for Last to always get the last value.
|
||||
instance Semigroup PartialConf where
|
||||
_a <> _b = PartialConf
|
||||
{ pcPort = error "pcPort (<>) 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
|
||||
-- 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'
|
||||
|
@ -7,7 +7,7 @@ module Level07.Conf
|
||||
import GHC.Word (Word16)
|
||||
|
||||
import Data.Bifunctor (first)
|
||||
import Data.Monoid (Last (..), (<>))
|
||||
import Data.Semigroup (Last (..), (<>))
|
||||
|
||||
import Level07.Types (Conf (..), ConfigError (..),
|
||||
DBFilePath (DBFilePath),
|
||||
@ -21,8 +21,8 @@ import Level07.Conf.File (parseJSONConfigFile)
|
||||
defaultConf
|
||||
:: PartialConf
|
||||
defaultConf = PartialConf
|
||||
(pure (Port 3000))
|
||||
(pure (DBFilePath "app_db.db"))
|
||||
(pure (Last $ Port 3000))
|
||||
(pure (Last $ DBFilePath "app_db.db"))
|
||||
|
||||
-- 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
|
||||
@ -39,10 +39,10 @@ makeConfig pc = Conf
|
||||
-- like to be explicit in your intentions.
|
||||
lastToEither
|
||||
:: ConfigError
|
||||
-> (PartialConf -> Last b)
|
||||
-> (PartialConf -> Maybe (Last b))
|
||||
-> Either ConfigError b
|
||||
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.
|
||||
-- Since it wraps all our efforts to read information from the command line, and
|
||||
|
@ -2,7 +2,7 @@ module Level07.Conf.CommandLine
|
||||
( commandLineParser
|
||||
) where
|
||||
|
||||
import Data.Monoid (Last (Last), (<>))
|
||||
import Data.Semigroup (Last (Last), (<>))
|
||||
|
||||
import Options.Applicative (Parser, eitherReader, execParser,
|
||||
fullDesc, header, help, helper, info,
|
||||
@ -11,7 +11,7 @@ import Options.Applicative (Parser, eitherReader, execParser,
|
||||
|
||||
import Text.Read (readEither)
|
||||
|
||||
import Level07.Types (DBFilePath (DBFilePath),
|
||||
import Level07.Types (DBFilePath (DBFilePath),
|
||||
PartialConf (PartialConf), Port (Port))
|
||||
|
||||
-- | Command Line Parsing
|
||||
@ -36,7 +36,7 @@ partialConfParser =
|
||||
|
||||
-- Parse the Port value off the command line args and into a Last wrapper.
|
||||
portParser
|
||||
:: Parser (Last Port)
|
||||
:: Parser (Maybe (Last Port))
|
||||
portParser =
|
||||
let
|
||||
mods = long "port"
|
||||
@ -46,11 +46,11 @@ portParser =
|
||||
-- A custom parser to turn a String into a Word16, before putting it into a Port
|
||||
portReader = eitherReader (fmap Port . readEither)
|
||||
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.
|
||||
dbFilePathParser
|
||||
:: Parser (Last DBFilePath)
|
||||
:: Parser (Maybe (Last DBFilePath))
|
||||
dbFilePathParser =
|
||||
let
|
||||
mods = long "db-filepath"
|
||||
@ -58,4 +58,4 @@ dbFilePathParser =
|
||||
<> metavar "DBFILEPATH"
|
||||
<> help "File path for our SQLite Database file."
|
||||
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.Functor.Contravariant ((>$<))
|
||||
import Data.Monoid (Last (Last))
|
||||
import Data.Semigroup (Semigroup ((<>)))
|
||||
import Data.Semigroup (Last (Last), Semigroup ((<>)))
|
||||
|
||||
import Data.Time (UTCTime)
|
||||
import qualified Data.Time.Format as TF
|
||||
@ -162,51 +161,42 @@ data ConfigError
|
||||
| ConfigFileReadError IOError
|
||||
deriving Show
|
||||
|
||||
-- Our application will be able to load configuration from both a file and
|
||||
-- command line input. We want to be able to use the command line to temporarily
|
||||
-- override the configuration from our file. How do we combine the different
|
||||
-- inputs to enable this property?
|
||||
-- Our application will be able to load configuration from both a file and command line
|
||||
-- input. We want to be able to use the command line to temporarily override the
|
||||
-- configuration from our file. How do we combine the different inputs to enable this
|
||||
-- property?
|
||||
|
||||
-- We want the command line 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:
|
||||
-- We want the command line 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:
|
||||
|
||||
-- ``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
|
||||
-- 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
|
||||
-- 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.
|
||||
-- ``Semigroup`` instance will always preference the last value that it has:
|
||||
--
|
||||
-- Just (Last 3) <> Just (Last 1) = Just (Last 1)
|
||||
-- Nothing <> Just (Last 1) = Just (Last 1)
|
||||
-- Just (Last 1) <> Nothing = Just (Last 1)
|
||||
--
|
||||
-- To make this easier, we'll make a new type ``PartialConf`` that will have our ``Last``
|
||||
-- wrapped values. We can then define a ``Semigroup`` instance for it and have our
|
||||
-- ``Conf`` be a known good configuration.
|
||||
data PartialConf = PartialConf
|
||||
{ pcPort :: Last Port
|
||||
, pcDBFilePath :: Last DBFilePath
|
||||
{ pcPort :: Maybe (Last Port)
|
||||
, pcDBFilePath :: Maybe (Last DBFilePath)
|
||||
}
|
||||
|
||||
-- Before we can define our ``Monoid`` instance for ``PartialConf``, we'll have
|
||||
-- to define a Semigroup instance. We define our ``(<>)`` function to lean
|
||||
-- on the ``Semigroup`` instance for Last to always get the last value.
|
||||
-- We need to define a ``Semigroup`` instance for ``PartialConf``. We define our ``(<>)``
|
||||
-- function to lean on the ``Semigroup`` instance for Last to always get the last value.
|
||||
instance Semigroup PartialConf where
|
||||
a <> b = PartialConf
|
||||
{ pcPort = pcPort a <> pcPort 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
|
||||
-- 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'
|
||||
@ -221,7 +211,7 @@ partialConfDecoder = PartialConf
|
||||
<$> lastAt "port" D.integral Port
|
||||
<*> lastAt "dbFilePath" D.string DBFilePath
|
||||
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
|
||||
-- our database queries. This also allows things to change over time without
|
||||
|
Loading…
Reference in New Issue
Block a user