Creating and Structuring Haskell Projects
Modularity is the key to building large applications in any language. This chapter will give you the information you need to be able to build large scale Haskell applications, both by creating your own modules to allow you to organize and reuse your code, as well as by making use of libraries developed by others.
You’ll learn how to use Haskell’s cabal tool to define, build, and run larger applications that are made up of multiple files and include external dependencies.
You’ll be able to make use of the extensive collection of libraries available on hackage to write new programs, and to build applications that you can easily build and distribute.
In the next few chapters you’ll start work on larger applications and will begin to make use of some of the common core Haskell libraries that are not included with the base system. As you start to build these large applications you’ll need to learn how to build projects and import dependencies.
Refactoring UserInfo
Refactor the HaskellBook.Examples.UserInfo
module into smaller modules. Try to
look at different ways that you can separate out the concerns of authentication,
looking up a particular user, and getting information about a user.
Hints
Click to reveal
You may want to rethink the export list from the module. Do you need to export more things if you break the module up? Are there ways around that?
Solution
Click to reveal
If we review the final version of our UserInfo
module from Chapter 5, you’ll
see that our module is doing several different things:
- Defines the
User
type, and helper functions for interacting with it - Creates a known set of users and lets us look up a user by name
- Provides a method for authenticating a user, given their password
At first glance it would seem like we could easily separate out these three concerns into three separate modules without any trouble. Looking a bit more closely though, we have a problem: In our original module, we opted not to export any functions directly related to the password. This let us ensure that anyone using our module couldn’t misuse the password in some insecure way. Unfortunately, we need access to be able to supply a password to each of our known users, and we need to be able to check to see if a password is valid if we want to build authentication. If we want to refactor the module, we’ll need to make some decisions. Let’s look at a few of our options. None of these choices are necessarily better than the others, they simply offer different tradeoffs.
Don’t Restrict the Password Field
Perhaps the simplest approach to refactoring our code is to break our module up into three new modules that:
- Define the
User
record - Provides a set of default users
- Handles finding and authenticating users
Let’s take a look at this approach to refactoring (Don’t worry about the change in module layout outside of this exercise, this is an artifact of the way the solutions are written for this site).
First, we’ll look at our new User
module. we’re exporting everything from
User
record now, so there’s no need for an explicit export list:
module EffectiveHaskell.Exercises.Chapter5.UserInfo.User where
data User isAuthenticated = User
userName :: String
{ userPassword :: String
, userEmailAddress :: String
, userInternetPoints :: Int
, }
Next let’s write our KnownUsers
module, which will container our predefined
user list:
module EffectiveHaskell.Exercises.Chapter5.UserInfo.KnownUsers where
import EffectiveHaskell.Exercises.Chapter5.UserInfo.User
users :: [User a]
= [george, porter]
users where
= User
george = "george"
{ userName = 1000
, userInternetPoints = "secret"
, userPassword = "gbird2015@example.com"
, userEmailAddress
}= User
porter = "porter"
{ userName = 500
, userInternetPoints = "hunter2"
, userPassword = "woofwoof@example.com"
, userEmailAddress }
You’ll notice that in this example we’re defining the list of users, but we
haven’t yet written our lookupUser
function. That will be included in the
Authentication
module that we’ll look at next. The reason for this is that
we’ve also not yet defined our Authenticated
and Unauthenticated
types. Since we want lookupUser
to return an unauthenticated user, we’ll need
Unauthenticated
to be in scope when we write lookupUser
. Let’s go ahead and
look at our Authentication
module now:
{-# LANGUAGE RecordWildCards #-}
module EffectiveHaskell.Exercises.Chapter5.UserInfo.Authentication where
import EffectiveHaskell.Exercises.Chapter5.UserInfo.User
import EffectiveHaskell.Exercises.Chapter5.UserInfo.KnownUsers
import Data.List
data Authenticated
data Unauthenticated
authenticateUser :: User Unauthenticated -> String -> Maybe (User Authenticated)
User{..} password
authenticateUser | userPassword == password = Just User{..}
| otherwise = Nothing
lookupUser :: String -> Maybe (User Unauthenticated)
=
lookupUser name -> userName user == name) users find (\user
Our Authentication
module needs to import both the User
module, for the
definition of the User
record, and the KnownUsers
module, for the list of
users.
Provide a High-Level Module With Select Re-Exports
Our earlier approach benefitted from requiring very little refactoring, but it
had a couple of drawbacks: The first problem was that, to interact with our
code, we’d often end up needing to import several of our modules. Before
refactoring, someone using our code would only have had to import a single
module. The second problem is that our refactoring required that we export
password information in our User
module. One way that we can address that is
to create a high level module that selectively re-exports some features out of
the three modules we’ve just defined. For example, we might want to export
everything except for the userPassword
field from User
:
module EffectiveHaskell.Exercises.Chapter5.UserInfo
module EffectiveHaskell.Exercises.Chapter5.UserInfo.User
( module EffectiveHaskell.Exercises.Chapter5.UserInfo.KnownUsers
, module EffectiveHaskell.Exercises.Chapter5.UserInfo.Authentication
,
)where
import EffectiveHaskell.Exercises.Chapter5.UserInfo.User hiding (userPassword)
import EffectiveHaskell.Exercises.Chapter5.UserInfo.KnownUsers
import EffectiveHaskell.Exercises.Chapter5.UserInfo.Authentication
Create An Alternative To Directly Accessing Passwords
So far we’ve looked at solutions that involved compromising on our original
design by exporting the password field for our User
type. That’s not the only
option we have. An alternative would be to add functions to the User
module to
let us do only the things that we need to do with a password. In our case, we
need to be able to test whether a given password attempt for a user is correct,
and we need to be able to create new users, including setting a password for
them. We can do that by adding two new functions. First, makeUser
will take
the place of the normal User
value constructor, and will let us set a
password:
makeUser :: String -> String -> String -> Int -> User a
= User
makeUser name passwd email points = name
{ userName = passwd
, userPassword = email
, userEmailAddress = points
, userInternetPoints }
Next, we can write a function called testUserPassword
that will tell us if a
password attempt for a user is correct or not:
testUserPassword :: User a -> String -> Bool
=
testUserPassword user passwordAttempt == userPassword user passwordAttempt
We’ll also update the export list for our User
module export our new
functions, and make sure it doesn’t export userPassword
:
module EffectiveHaskell.Exercises.Chapter5.UserInfo.User
User
(
, userName
, userEmailAddress
, userInternetPoints
, makeUser
, testUserPasswordwhere )
Once we’ve finished up our changes to User
we’ll need to update KnownUsers
and Authentication
as well. Let’s start by updating KnownUsers
to use
makeUser
:
module EffectiveHaskell.Exercises.Chapter5.UserInfo.KnownUsers where
import EffectiveHaskell.Exercises.Chapter5.UserInfo.User
users :: [User a]
= [george, porter]
users where
= makeUser "george" "secret" "gbird2015@example.com" 1000
george = makeUser "porter" "hunter2" "woofwoof@example.com" 500 porter
As you can see, there’s not much that needs to change in our known user
definitions. Let’s move onto Authentication
where we’ll need to change the
definition of authenticatedUser
to call testUserPassword
instead of testing
the password directly. This function doesn’t need to be changed much, but we’ll
need to stop using record wildcards now that we’re no longer exporting the
User
constructor.
module EffectiveHaskell.Exercises.Chapter5.UserInfo.Authentication where
import EffectiveHaskell.Exercises.Chapter5.UserInfo.User
import EffectiveHaskell.Exercises.Chapter5.UserInfo.KnownUsers
import Data.List
data Authenticated
data Unauthenticated
authenticateUser :: User Unauthenticated -> String -> Maybe (User Authenticated)
authenticateUser user password| testUserPassword user password = Just $ makeUser name password email points
| otherwise = Nothing
where
= userName user
name = userEmailAddress user
email = userInternetPoints user
points
lookupUser :: String -> Maybe (User Unauthenticated)
=
lookupUser name -> userName user == name) users find (\user
Which Approach is Most Common?
All of the approaches we’ve looked at in this exercise are things you might see in a real codebase- including the original version that didn’t break the larger module up into smaller components. If you do opt for doing a refactor, choosing a higher level module that has a more restrictive export list than the lower level modules it re-exports from is likely the most common and has the best ergonomics.
Old New Projects
Use cabal and create projects for all of the examples that you’ve already worked on as you’ve been working through this book. Consider how you might organize the modules to maximize re-use in cases where we worked through several variations of a single example.
Hints
Click to reveal
There aren’t any tricks to this question. This is a chance for you to get some practice working with cabal files, and to get in the habit of creating proper projects before you move on to the larger examples in the rest of the book. Feel free to look at the solution for a large example of a cabal file, or refer to any of the examples in the chapter.
Solution
Click to reveal
Here’s a complete example of the set cabal file that was used to build all of
the examples and tests for Effective Haskell. You’ll notice that in cases where
we have many variations of an example, we create modules with names like V1
,
V2
and so on. These modules can often import shared code that doesn’t need to
change between versions, allowing us to reduce the amount of rework that happens
between multiple versions of the code. In the real world you’d likely use
version control for these sorts of incremental updates, but having multiple
versions of an API can be a useful way of preserving backwards compatibility.
cabal-version: 3.0
name: effective-haskell-examples
version: 0
license: BSD-2-Clause
license-file: LICENSE
build-type: Simple
library
exposed-modules:
EffectiveHaskell
EffectiveHaskell.Chapter1
EffectiveHaskell.Chapter1.Branches
EffectiveHaskell.Chapter1.ComposingFunctions
EffectiveHaskell.Chapter1.LetBindings
EffectiveHaskell.Chapter1.Looping
EffectiveHaskell.Chapter1.OperatorExample
EffectiveHaskell.Chapter1.OperatorExample.InfixL
EffectiveHaskell.Chapter1.OperatorExample.InfixL6
EffectiveHaskell.Chapter1.OperatorExample.InfixL7
EffectiveHaskell.Chapter1.OperatorExample.InfixL8
EffectiveHaskell.Chapter1.OperatorExample.InfixR0
EffectiveHaskell.Chapter1.OperatorExample.InfixR6
EffectiveHaskell.Chapter1.OperatorExample.InfixR7
EffectiveHaskell.Chapter1.OperatorExample.InfixR8
EffectiveHaskell.Chapter1.OperatorExample.InfixR9
EffectiveHaskell.Chapter1.Precedence
EffectiveHaskell.Chapter1.Salutation.Version1
EffectiveHaskell.Chapter1.Salutation.Version2
EffectiveHaskell.Chapter10
EffectiveHaskell.Chapter10.MetricsV1
EffectiveHaskell.Chapter10.MetricsV2
EffectiveHaskell.Chapter10.V1
EffectiveHaskell.Chapter10.V2
EffectiveHaskell.Chapter11
EffectiveHaskell.Chapter11.ExistentialDemo
EffectiveHaskell.Chapter11.V1
EffectiveHaskell.Chapter11.V2
EffectiveHaskell.Chapter11.V3
EffectiveHaskell.Chapter11.V4
EffectiveHaskell.Chapter12
EffectiveHaskell.Chapter12.V1
EffectiveHaskell.Chapter12.V2
EffectiveHaskell.Chapter12.V3
EffectiveHaskell.Chapter12.V4
EffectiveHaskell.Chapter12.V5
EffectiveHaskell.Chapter13
EffectiveHaskell.Chapter13.Archiver
EffectiveHaskell.Chapter13.ClassyArchiver
EffectiveHaskell.Chapter13.EitherIO
EffectiveHaskell.Chapter13.ExceptState
EffectiveHaskell.Chapter13.ExceptT
EffectiveHaskell.Chapter13.ExceptTParser
EffectiveHaskell.Chapter13.FailingStatefulParser
EffectiveHaskell.Chapter13.Identity
EffectiveHaskell.Chapter13.MonadError
EffectiveHaskell.Chapter13.MonadIO
EffectiveHaskell.Chapter13.MonadIODemo
EffectiveHaskell.Chapter13.MonadState
EffectiveHaskell.Chapter13.MonadState.V1
EffectiveHaskell.Chapter13.MonadStateDemo
EffectiveHaskell.Chapter13.MonadStateDemo.V1
EffectiveHaskell.Chapter13.MonadStateDemo.V2
EffectiveHaskell.Chapter13.MonadStateDemo.V3
EffectiveHaskell.Chapter13.MonadStateDemo.V4
EffectiveHaskell.Chapter13.MonadTrans
EffectiveHaskell.Chapter13.MonadTrans.V1
EffectiveHaskell.Chapter13.MonadTrans.V2
EffectiveHaskell.Chapter13.State
EffectiveHaskell.Chapter13.StateExcept
EffectiveHaskell.Chapter13.StateParser
EffectiveHaskell.Chapter13.StateT
EffectiveHaskell.Chapter14
EffectiveHaskell.Chapter14.SpellCheck
EffectiveHaskell.Chapter14.SpellCheck.ListMemo
EffectiveHaskell.Chapter14.SpellCheck.LowLevelUnboxed
EffectiveHaskell.Chapter14.SpellCheck.Naive
EffectiveHaskell.Chapter14.SpellCheck.STMemo
EffectiveHaskell.Chapter14.SpellCheck.STVec
EffectiveHaskell.Chapter14.SpellCheck.Types
EffectiveHaskell.Chapter14.SpellCheck.Types.V1
EffectiveHaskell.Chapter14.SpellCheck.VectorDemo
EffectiveHaskell.Chapter15
EffectiveHaskell.Chapter15.ClosedTypeFamilyDemo
EffectiveHaskell.Chapter15.ColorDemo
EffectiveHaskell.Chapter15.CommandRunner
EffectiveHaskell.Chapter15.CommandRunner.V1
EffectiveHaskell.Chapter15.CommandRunner.V2
EffectiveHaskell.Chapter15.GADTs.V1
EffectiveHaskell.Chapter15.GADTShellCmd
EffectiveHaskell.Chapter15.OpenDataFamiliesDemo
EffectiveHaskell.Chapter15.OpenDataFamiliesDemo.V1
EffectiveHaskell.Chapter15.ShellCommand.V1
EffectiveHaskell.Chapter15.ShellCommand.V2
EffectiveHaskell.Chapter15.ShellCommand.V3
EffectiveHaskell.Chapter15.TypeFamilyListFuncs
EffectiveHaskell.Chapter15.TypeFamilyListFuncs.V1
EffectiveHaskell.Chapter15.TypeFamilyListFuncs.V2
EffectiveHaskell.Chapter15.TypeLevelList
EffectiveHaskell.Chapter2
EffectiveHaskell.Chapter2.Fibs
EffectiveHaskell.Chapter2.Fibs.V1
EffectiveHaskell.Chapter2.Fibs.V2
EffectiveHaskell.Chapter3
EffectiveHaskell.Chapter3.PolymorphicFunctions
EffectiveHaskell.Chapter3.TypeErrors
EffectiveHaskell.Chapter3.WritingTypeAnnotationsForFunctions
EffectiveHaskell.Chapter4
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecords
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecords.Aliases
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecords.Calculator
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecords.DuplicateFields
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecords.Lists
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecords.Parser
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecords.Peano
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecords.PolymorphicTypes
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecords.Records
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecords.SumTypes
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecords.Wildcards
EffectiveHaskell.Chapter5
EffectiveHaskell.Chapter6
EffectiveHaskell.Chapter6.AdHocPolymorphism
EffectiveHaskell.Chapter6.AdHocPolymorphism.ClassNatural
EffectiveHaskell.Chapter6.AdHocPolymorphism.Deriving
EffectiveHaskell.Chapter6.AdHocPolymorphism.Deriving.Derived
EffectiveHaskell.Chapter6.AdHocPolymorphism.Deriving.ManualInstances
EffectiveHaskell.Chapter6.AdHocPolymorphism.DerivingVia.Manual
EffectiveHaskell.Chapter6.AdHocPolymorphism.DerivingVia.Via
EffectiveHaskell.Chapter6.AdHocPolymorphism.DerivingViaDemo
EffectiveHaskell.Chapter6.AdHocPolymorphism.Eq
EffectiveHaskell.Chapter6.AdHocPolymorphism.HKTDemo
EffectiveHaskell.Chapter6.AdHocPolymorphism.Naive
EffectiveHaskell.Chapter6.AdHocPolymorphism.NewtypeDemo
EffectiveHaskell.Chapter6.AdHocPolymorphism.OrdExample
EffectiveHaskell.Chapter6.AdHocPolymorphism.OrdExampleAlternateDefaults
EffectiveHaskell.Chapter6.AdHocPolymorphism.OrdExampleDefaults
EffectiveHaskell.Chapter6.AdHocPolymorphism.OrdExampleDefaultsWithMinMax
EffectiveHaskell.Chapter6.AdHocPolymorphism.RecordNatural
EffectiveHaskell.Chapter6.AdHocPolymorphism.Redacted
EffectiveHaskell.Chapter6.AdHocPolymorphism.TypeApplications
EffectiveHaskell.Chapter6.AdHocPolymorphism.USD
EffectiveHaskell.Chapter6.AdHocPolymorphism.USD.Auto
EffectiveHaskell.Chapter6.AdHocPolymorphism.USD.Manual
EffectiveHaskell.Chapter7
EffectiveHaskell.Chapter7.Examples
EffectiveHaskell.Chapter7.SumArguments
EffectiveHaskell.Chapter8
EffectiveHaskell.Chapter8.Examples
EffectiveHaskell.Chapter8.Examples.V1
EffectiveHaskell.Chapter8.Examples.V2
EffectiveHaskell.Chapter8.Examples.V3
EffectiveHaskell.Chapter8.Examples.V4
EffectiveHaskell.Chapter8.Examples.V5
EffectiveHaskell.Chapter9
EffectiveHaskell.Chapter9.Functors
EffectiveHaskell.Chapter9.Functors.V1
EffectiveHaskell.Fifo
MyLib
other-modules:
Paths_effective_haskell_examples
autogen-modules:
Paths_effective_haskell_examples
hs-source-dirs:
src
ghc-options: -Wall -O2
build-depends:
async
, base
, base64-bytestring
, bytestring
, containers
, directory
, filepath
, mtl
, process
, text
, time
, transformers
, unix
, vector
default-language: Haskell2010
executable effective-haskell-demos
main-is: Main.hs
other-modules:
Paths_effective_haskell_examples
autogen-modules:
Paths_effective_haskell_examples
hs-source-dirs:
app
ghc-options: -Wall -O2
build-depends:
async
, base
, base64-bytestring
, bytestring
, containers
, directory
, effective-haskell-examples
, filepath
, mtl
, process
, text
, time
, transformers
, unix
, vector
default-language: Haskell2010
test-suite test
type: exitcode-stdio-1.0
main-is: Spec.hs
other-modules:
EffectiveHaskell.Chapter1.BranchesSpec
EffectiveHaskell.Chapter1.ComposingFunctionsSpec
EffectiveHaskell.Chapter1.LetBindingsSpec
EffectiveHaskell.Chapter1.LoopingSpec
EffectiveHaskell.Chapter1.OperatorExampleSpec
EffectiveHaskell.Chapter1.PrecedenceSpec
EffectiveHaskell.Chapter10.MetricsV1Spec
EffectiveHaskell.Chapter10.MetricsV2Spec
EffectiveHaskell.Chapter10.V1Spec
EffectiveHaskell.Chapter10.V2Spec
EffectiveHaskell.Chapter10Spec
EffectiveHaskell.Chapter11.ExistentialDemoSpec
EffectiveHaskell.Chapter11.V1Spec
EffectiveHaskell.Chapter11.V2Spec
EffectiveHaskell.Chapter11.V3Spec
EffectiveHaskell.Chapter11.V4Spec
EffectiveHaskell.Chapter11Spec
EffectiveHaskell.Chapter12.V1Spec
EffectiveHaskell.Chapter12.V2Spec
EffectiveHaskell.Chapter12.V3Spec
EffectiveHaskell.Chapter12.V4Spec
EffectiveHaskell.Chapter12.V5Spec
EffectiveHaskell.Chapter12Spec
EffectiveHaskell.Chapter13.ClassyArchiverSpec
EffectiveHaskell.Chapter13.EitherIOSpec
EffectiveHaskell.Chapter13.ExceptStateSpec
EffectiveHaskell.Chapter13.ExceptTParserSpec
EffectiveHaskell.Chapter13.ExceptTSpec
EffectiveHaskell.Chapter13.MonadIODemoSpec
EffectiveHaskell.Chapter13.MonadStateDemoSpec
EffectiveHaskell.Chapter13.StateExceptSpec
EffectiveHaskell.Chapter13.StateSpec
EffectiveHaskell.Chapter13Spec
EffectiveHaskell.Chapter14.SpellCheck.ListMemoSpec
EffectiveHaskell.Chapter14.SpellCheck.NaiveSpec
EffectiveHaskell.Chapter14.SpellCheck.STMemoSpec
EffectiveHaskell.Chapter14.SpellCheck.TypesSpec
EffectiveHaskell.Chapter14.SpellCheckSpec
EffectiveHaskell.Chapter15.ClosedTypeFamilyDemoSpec
EffectiveHaskell.Chapter15.CommandRunner.V2Spec
EffectiveHaskell.Chapter15.CommandRunnerSpec
EffectiveHaskell.Chapter15.GADTs.V1Spec
EffectiveHaskell.Chapter15.OpenDataFamiliesDemo.V1Spec
EffectiveHaskell.Chapter15.OpenDataFamiliesDemoSpec
EffectiveHaskell.Chapter15.ShellCommand.V1Spec
EffectiveHaskell.Chapter15.ShellCommand.V2Spec
EffectiveHaskell.Chapter15.ShellCommand.V3Spec
EffectiveHaskell.Chapter15.TypeFamilyListFuncs.V1Spec
EffectiveHaskell.Chapter15.TypeFamilyListFuncs.V2Spec
EffectiveHaskell.Chapter15.TypeFamilyListFuncsSpec
EffectiveHaskell.Chapter15.TypeLevelListSpec
EffectiveHaskell.Chapter15Spec
EffectiveHaskell.Chapter2.FibsSpec
EffectiveHaskell.Chapter3.PolymorphicFunctionsSpec
EffectiveHaskell.Chapter4.CreatingDataTypesAndRecordsSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.ClassNaturalSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.DerivingSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.DerivingViaDemoSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.HKTDemoSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.NaiveSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.NewtypeDemoSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.OrdExampleAlternateDefaultsSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.OrdExampleDefaultsSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.OrdExampleDefaultsWithMinMaxSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.OrdExampleSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.RecordNaturalSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.RedactedSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.TypeApplicationsSpec
EffectiveHaskell.Chapter6.AdHocPolymorphism.USDSpec
EffectiveHaskell.Chapter6.AdHocPolymorphismSpec
EffectiveHaskell.Chapter7.ExamplesSpec
EffectiveHaskell.Chapter7.SumArgumentsSpec
EffectiveHaskell.Chapter8.Examples.V1Spec
EffectiveHaskell.Chapter8.Examples.V2Spec
EffectiveHaskell.Chapter8.Examples.V3Spec
EffectiveHaskell.Chapter8.Examples.V4Spec
EffectiveHaskell.Chapter8.Examples.V5Spec
EffectiveHaskell.Chapter8.ExamplesSpec
EffectiveHaskell.Chapter9.Functors.V1Spec
EffectiveHaskell.Chapter9.FunctorsSpec
EffectiveHaskell.Chapter9Spec
EffectiveHaskell.TestUtils
Paths_effective_haskell_examples
autogen-modules:
Paths_effective_haskell_examples
hs-source-dirs:
test
ghc-options: -Wall
build-depends:
async
, base
, base64-bytestring
, bytestring
, containers
, directory
, effective-haskell-examples
, filepath
, hspec
, mtl
, process
, text
, time
, transformers
, unix
, vector
default-language: Haskell2010
Document Your Modules
Review the projects that you created in this chapter, as well as any cabal projects you created while working through previous examples, and document them. Make sure to check out the official haddock documentation to find out about more ways that you can effectively format your documentation.
Hints
Click to reveal
There’s no trick to this question, it’s merely a chance for you to get practice writing documentation.
Solution
Click to reveal
Here’s an example of our refactored User
module:
module EffectiveHaskell.Exercises.Chapter5.UserInfo.User
-- * User and Accessor Info
( -- | A 'User' can be some user with an account on a
-- system. Although users have a password, it should be treated as
-- opaque in most cases. You can test whether a password is
-- correct using the 'testUserPassword' function
User
, userName
, userEmailAddress
, userInternetPoints-- * Creating new users
-- | Although this module supports creating new users with
-- 'makeUser' you may be more interested in the
-- 'EffectiveHaskell.Exercises.Chapter5.UserInfo.KnownUsers.users'
-- collection of known users from
-- 'EffectiveHaskell.Exercises.Chapter5.UserInfo.KnownUsers'
, makeUser-- * Authentication
-- | You can build authentication using these functions.
, testUserPasswordwhere
)
-- | A User represents some user on the system. They have a username,
-- password, and contact information, along with some points.
data User isAuthenticated = User
userName :: String
{-- ^ The username. We use this to lookup users, and for public identification.
userPassword :: String
,-- ^ A users password. Try to keep this private. You can't access
-- this directly, but you can test whether a password matches or
-- not with 'testUserPassword'
userEmailAddress :: String
,-- ^ A users email address.
userInternetPoints :: Int
,-- ^ How many internet points does this user have?
}
-- | Create a new user.
-- example:
--
-- @
-- makeUser "username1" "hunder2" "user@example.com" 100
-- @
--
makeUser ::
String -> -- ^ @name@: The username to be assigned to the new user
String -> -- ^ @passwd@: The password for the new user
String -> -- ^ @email@: The new user's contact information
Int -> -- ^ @points@: Initial number of internet points to assign
User a
= User
makeUser name passwd email points = name
{ userName = passwd
, userPassword = email
, userEmailAddress = points
, userInternetPoints
}
-- | Given a user and a password, return 'True' if the password matches the given user's password
testUserPassword ::
User a -> -- ^ @user@: The user whose password should be tested
String -> -- ^ @passwordAttempt@: A password to attempt
Bool
=
testUserPassword user passwordAttempt == userPassword user passwordAttempt