1
1
mirror of https://github.com/qfpl/applied-fp-course.git synced 2024-11-26 14:43:53 +03:00

Pin nixpkgs

Reorder the modules in level04 to make the order more clear.

Add explicit exports to the AppM modules in 5,6, and 7 to help make it clearer
that you don't need to use the explicit constructors.

Update type signatures for config file functions in level 6 to use the newly
generalised AppM to take advantage of the variable error type.
This commit is contained in:
Sean Chalmers 2018-12-14 13:15:08 +10:00
parent b8921cd1aa
commit 1ce56dcb0c
8 changed files with 59 additions and 24 deletions

View File

@ -1,20 +1,28 @@
{ nixpkgs ? import <nixpkgs> {} { compiler ? "default"
, compiler ? "default"
}: }:
let let
inherit (nixpkgs) pkgs; inherit (import <nixpkgs> {}) fetchFromGitHub;
nixpkgs = fetchFromGitHub {
owner = "NixOS";
repo = "nixpkgs-channels";
rev = "4f3446f29910d21eb0fb942bd03091b089cdad63";
sha256 = "0dqjkhhhckp881mns69qxn4dngcykal1gqrpaf9qy2vja4i41ay5";
};
pkgs = import nixpkgs {};
# Grab our course derivation # Grab our course derivation
course = import ./. { inherit nixpkgs compiler; }; course = import ./. { nixpkgs = pkgs; inherit compiler; };
# Override the basic derivation so we can have a more fully feature # Override the basic derivation so we can have a more fully feature
# environment for hacking on the course material # environment for hacking on the course material
courseDevEnv = (pkgs.haskell.lib.addBuildTools course courseDevEnv = (pkgs.haskell.lib.addBuildTools course
[ # Include the SQLite Database application [ # Include the SQLite Database application
nixpkgs.sqlite pkgs.sqlite
# 'ghcid' auto reloading tool # 'ghcid' auto reloading tool
nixpkgs.haskellPackages.ghcid pkgs.haskellPackages.ghcid
] ]
# We don't want nix to build the thing, we want the environment so we can # We don't want nix to build the thing, we want the environment so we can
# build the thing. # build the thing.

View File

@ -65,11 +65,11 @@ function into the module scope.
## Steps for this level: ## Steps for this level:
The steps for this level: The steps for this level:
1) ``src/Level04/DB/Types.hs`` 1) ``src/Level04/Types/Topic.hs``
2) ``src/Level04/DB.hs`` 2) ``src/Level04/Types/CommentText.hs``
3) ``src/Level04/Types/Topic.hs`` 3) ``src/Level04/DB/Types.hs``
4) ``src/Level04/Types/CommentText.hs`` 4) ``src/Level04/Types.hs``
5) ``src/Level04/Types.hs`` 5) ``src/Level04/DB.hs``
6) ``src/Level04/Core.hs`` 6) ``src/Level04/Core.hs``
For the sake of simplicity, any configuration requirements will be hardcoded in For the sake of simplicity, any configuration requirements will be hardcoded in

View File

@ -1,7 +1,11 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
module Level05.AppM where module Level05.AppM
( AppM
, liftEither
, runAppM
) where
import Control.Monad.Except (MonadError (..)) import Control.Monad.Except (MonadError (..))
import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.IO.Class (MonadIO (..))

View File

@ -1,14 +1,22 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
module Level06.AppM where module Level06.AppM
( AppM
, App
, liftEither
, runAppM
, runApp
) where
import Control.Monad.Except (MonadError (..)) import Control.Monad.Except (MonadError (..))
import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.IO.Class (MonadIO (..))
import Data.Bifunctor (Bifunctor (..))
import Level06.Types (Error) import Level06.Types (Error)
-- | We're going to upgrade the capability of our AppM by generalising the type of the errors that -- We're going to upgrade the capability of our AppM by generalising the type of the errors that
-- it handles. This means that we'll be able to reuse our 'AppM' in more places that maybe have an -- it handles. This means that we'll be able to reuse our 'AppM' in more places that maybe have an
-- overabundance of 'IO (Either e a)' types. -- overabundance of 'IO (Either e a)' types.
-- --
@ -19,20 +27,20 @@ newtype AppM e a = AppM
{ runAppM :: IO (Either e a) { runAppM :: IO (Either e a)
} }
-- | Predominantly our application has only one error type: 'Error'. It would be tedious to have to -- Predominantly our application has only one error type: 'Error'. It would be tedious to have to
-- declare that on every signature. We're able to use a type _alias_ to avoid this problem. We can -- declare that on every signature. We're able to use a type _alias_ to avoid this problem. We can
-- define this type alias to make the error type variable concrete as 'Error'. -- define this type alias to make the error type variable concrete as 'Error'.
-- --
type App = AppM Error type App = AppM Error
-- | We need to refactor the 'runAppM' function as now the name conflicts, and it needs to suit the -- We need to refactor the 'runAppM' function as now the name conflicts, and it needs to suit the
-- specialised 'App' type. The definition is even simpler than before. If someone near you is up to -- specialised 'App' type. The definition is even simpler than before. If someone near you is up to
-- the same section, try to explain to each other why this works. -- the same section, try to explain to each other why this works.
-- --
runApp :: App a -> IO (Either Error a) runApp :: App a -> IO (Either Error a)
runApp = runAppM runApp = runAppM
-- | You may copy your previously completed AppM instances here and then refactor them to suit the -- You may copy your previously completed AppM instances here and then refactor them to suit the
-- more generalised type of AppM. -- more generalised type of AppM.
-- | ----------------------------------------------------------------------------------------------- -- | -----------------------------------------------------------------------------------------------
@ -68,6 +76,13 @@ instance MonadError e (AppM e) where
catchError :: AppM e a -> (e -> AppM e a) -> AppM e a catchError :: AppM e a -> (e -> AppM e a) -> AppM e a
catchError = error "catchError for (AppM e) not implemented" catchError = error "catchError for (AppM e) not implemented"
-- The 'Bifunctor' instance for 'Either' has proved useful several times
-- already. Now that our 'AppM' exposes both type variables that are used in our
-- 'Either', we can define a Bifunctor instance and reap similar benefits.
instance Bifunctor AppM where
bimap :: (e -> d) -> (a -> b) -> AppM e a -> AppM d b
bimap = error "bimap for AppM not implemented"
-- This is a helper function that will `lift` an Either value into our new AppM -- This is a helper function that will `lift` an Either value into our new AppM
-- by applying `throwError` to the Left value, and using `pure` to lift the -- by applying `throwError` to the Left value, and using `pure` to lift the
-- Right value into the AppM. -- Right value into the AppM.

View File

@ -46,7 +46,7 @@ makeConfig =
-- --
parseOptions parseOptions
:: FilePath :: FilePath
-> IO (Either ConfigError Conf) -> AppM ConfigError Conf
parseOptions = parseOptions =
-- Parse the options from the config file: "files/appconfig.json" -- Parse the options from the config file: "files/appconfig.json"
-- Parse the options from the commandline using 'commandLineParser' -- Parse the options from the commandline using 'commandLineParser'

View File

@ -16,6 +16,7 @@ import Waargonaut (Json, parseWaargonaut)
import qualified Waargonaut.Decode as D import qualified Waargonaut.Decode as D
import Waargonaut.Decode.Error (DecodeError (ParseFailed)) import Waargonaut.Decode.Error (DecodeError (ParseFailed))
import Level06.AppM (AppM)
import Level06.Types (ConfigError (BadConfFile), import Level06.Types (ConfigError (BadConfFile),
PartialConf (PartialConf)) PartialConf (PartialConf))
-- $setup -- $setup
@ -33,7 +34,7 @@ import Level06.Types (ConfigError (BadConfFile),
-- --
readConfFile readConfFile
:: FilePath :: FilePath
-> IO (Either ConfigError ByteString) -> AppM ConfigError ByteString
readConfFile = readConfFile =
error "readConfFile not implemented" error "readConfFile not implemented"
@ -41,7 +42,7 @@ readConfFile =
-- and construct our ``PartialConf``. -- and construct our ``PartialConf``.
parseJSONConfigFile parseJSONConfigFile
:: FilePath :: FilePath
-> IO (Either ConfigError PartialConf) -> AppM ConfigError PartialConf
parseJSONConfigFile = parseJSONConfigFile =
error "parseJSONConfigFile not implemented" error "parseJSONConfigFile not implemented"
where where

View File

@ -2,7 +2,13 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
module Level07.AppM where module Level07.AppM
( AppM (..)
, App
, Env (..)
, liftEither
, runApp
) where
import Control.Monad.Except (MonadError (..)) import Control.Monad.Except (MonadError (..))
import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.IO.Class (MonadIO (..))

View File

@ -69,8 +69,9 @@ runApplication = do
runWithDBConn env = runWithDBConn env =
appWithDB env >> DB.closeDB (envDB env) appWithDB env >> DB.closeDB (envDB env)
appWithDB env = appWithDB env = Ex.finally
Ex.finally (run ( confPortToWai $ envConfig env ) (app env)) (DB.closeDB (envDB env)) (run ( confPortToWai . envConfig $ env ) (app env))
$ DB.closeDB (envDB env)
-- | Our AppM is no longer useful for implementing this function. Can you explain why? -- | Our AppM is no longer useful for implementing this function. Can you explain why?
-- --