Last active
July 1, 2020 10:34
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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