Creating and Structuring Haskell Projects

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]
users = [george, porter]
  where
    george = User
      { userName = "george"
      , userInternetPoints = 1000
      , userPassword = "secret"
      , userEmailAddress = "gbird2015@example.com"
      }
    porter = User
      { userName = "porter"
      , userInternetPoints = 500
      , userPassword = "hunter2"
      , userEmailAddress = "woofwoof@example.com"
      }

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)
authenticateUser User{..} password
  | userPassword == password = Just User{..}
  | otherwise = Nothing

lookupUser :: String -> Maybe (User Unauthenticated)
lookupUser name =
  find (\user -> userName user == name) users

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
makeUser name passwd email points = User
  { userName = name
  , userPassword = passwd
  , userEmailAddress = email
  , userInternetPoints = points
  }

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 =
  passwordAttempt == userPassword user

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
  , testUserPassword
  ) where

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]
users = [george, porter]
  where
    george = makeUser "george" "secret" "gbird2015@example.com" 1000
    porter = makeUser "porter" "hunter2" "woofwoof@example.com" 500

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
   name = userName user
   email = userEmailAddress user
   points = userInternetPoints user

lookupUser :: String -> Maybe (User Unauthenticated)
lookupUser name =
  find (\user -> userName user == name) users

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.
  , testUserPassword
  ) where

-- | 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
makeUser name passwd email points = User
  { userName = name
  , userPassword = passwd
  , userEmailAddress = email
  , userInternetPoints = points
  }

-- | 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 =
  passwordAttempt == userPassword user