Skip to content

Instantly share code, notes, and snippets.

@mostalive
Last active July 1, 2020 10:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mostalive/b07c90f93641e7b33cf465739c5212ba to your computer and use it in GitHub Desktop.
Save mostalive/b07c90f93641e7b33cf465739c5212ba to your computer and use it in GitHub Desktop.
Bucket constraint example from the Domain-Driven Design book, in purescript with hopefully a twist (work in progress)
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (logShow)
import TryPureScript (render, withConsole)
import Data.Maybe (Maybe(..))
import Data.Tuple (Tuple(..))
-- | Use micro-types instead of elementary data types. Second step: upgrade type to newtype
newtype Volume = Volume Int
instance showVolume :: Show Volume where
show (Volume i) = show i
derive instance eqVolume :: Eq Volume
derive instance ordVolume :: Ord Volume
-- Volume can not be negative
mkVolume :: Int -> Maybe Volume
mkVolume n | n >= 0 = Just (Volume n)
| otherwise = Nothing
-- No maybe, because we can assume a Volume satisfies its' invariant and is not negative
-- so adding two volumes is always positive
addVolume :: Volume -> Volume -> Volume
addVolume (Volume v1) (Volume v2) = (Volume (v1 + v2))
-- now this could go negative if v2 > v1, and break the invariant for volume
-- precondition: v1 > v2 , otherwise we need a Maybe
-- precondition requires exceptions, which makes our code impure
-- gut feeling - Maybe Volume is tolerable
-- property: given v1 >= v2 (precondition), we always get a Just _
-- given v1 < v2 (broken precondition), we always get Nothing
minus :: Volume -> Volume -> Maybe Volume
minus (Volume v1) (Volume v2) | v1 - v2 >= 0 = Just (Volume (v1 - v2))
| otherwise = Nothing
type Capacity = Volume -- so that we keep comparing them for now
data Bucket = Bucket Capacity Volume
derive instance eqBucket :: Eq Bucket
instance showBucket :: Show Bucket
where
show (Bucket capacity volume) = "Bucket, Capacity: " <> (show capacity) <> " Volume: " <> (show volume)
instance ordBucket :: Ord Bucket
where
compare (Bucket _ v1) (Bucket _ v2) = compare v1 v2
empty :: Volume
empty = Volume 0 -- raw data constructor, we know at this point that volume is not negative
mkBucket :: Capacity -> Maybe Bucket
mkBucket capacity = Just (Bucket capacity empty)
-- Returns the difference when not all water fits in the bucket
-- In the original, water disappears from the system when the bucket goes over capacity
-- I have fallen into the rabbit hole, done too much immutable programming...
-- babystep: the maybe is survivable - it only affects the overflow Volume, which is sort of a
-- byproduct
-- postcondition: the total volume in the system is the same after, as it was before
fillBucket :: Bucket -> Volume -> Tuple Bucket (Maybe Volume)
fillBucket (Bucket c v) v_fill | (addVolume v v_fill) <= c = Tuple (Bucket c (addVolume v v_fill)) (Just empty)
| otherwise = Tuple (Bucket c c) ((addVolume v v_fill) `minus` c)
main :: Effect Unit
main = render =<< withConsole do
logShow ((mkVolume 10) >>= mkBucket) -- pipeline, so we can unwrap the maybe.
-- (learnt the hard way that Maybe is only for the result of a function, not an input)
-- leads to fewer 'ifs' and clearer flow. Certain inputs, guarded outputs - there is probably a better name for this.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment