Last active
February 3, 2020 22:30
-
-
Save boj/7ad0423e1ca10b7f0076a22bfb8b64e1 to your computer and use it in GitHub Desktop.
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
{-# LANGUAGE DataKinds #-} | |
{-# LANGUAGE KindSignatures #-} | |
{-# LANGUAGE RecordWildCards #-} | |
module Main where | |
-------------------------------------------------------------------------------- | |
import Data.Coerce | |
import Data.Foldable | |
-------------------------------------------------------------------------------- | |
-- a data type that must start in the Unbuilt state | |
-- and must end in the Built state to be considered safe | |
data Buildable = Unbuilt | Built | |
-- wrapper for an arbitrary data type which has been queried | |
newtype Queried a = Queried { unQueried :: a } | |
-------------------------------------------------------------------------------- | |
data DataASource = DataASource Int deriving (Show) | |
getDataASources :: Int -> IO (Queried [DataASource]) | |
getDataASources _ = | |
pure (Queried (fmap DataASource [1..5])) -- imagine an IO query here | |
-------------------------------------------------------------------------------- | |
data DataA | |
= DataA | |
{ iid :: Int | |
} deriving (Show) | |
-- something similar to Beam -> Internal conversion | |
dasToDa :: DataASource -> DataA | |
dasToDa (DataASource i) = DataA i | |
-------------------------------------------------------------------------------- | |
data DataB (b :: Buildable) | |
= DataB | |
{ datas :: [DataA] | |
} deriving (Show) | |
-- this should be the only way to construct a new `DataB` | |
-- via exported smart constructors | |
mkDataB :: DataB Unbuilt | |
mkDataB = DataB [] -- perhaps all but the internal list is initialized | |
-- in order to get a `DataB Built` it must go | |
-- through this function, and only this function | |
-- | |
-- an empty list cannot be passed, it must be explicitly `Queried` | |
builtDataB :: DataB Unbuilt -> Queried [DataASource] -> DataB Built | |
builtDataB db@DataB{} das = coerce (db { datas = fmap dasToDa (unQueried das) }) | |
getDataBById :: Int -> IO (DataB Unbuilt) | |
getDataBById _ = pure mkDataB -- imagine an IO query here | |
-------------------------------------------------------------------------------- | |
-- logic | |
-------------------------------------------------------------------------------- | |
buildDataB :: Int -> DataB Unbuilt -> IO (DataB Built) | |
buildDataB i dbb = do | |
das <- getDataASources i -- based on related id | |
pure (builtDataB dbb das) -- returns a full `DataB Built` | |
-- queries a `DataB` by some `Int` id | |
businessLogic :: Int -> IO (DataB Built) | |
businessLogic i = do | |
db <- getDataBById i -- unbuilt `DataB` | |
buildDataB i db -- built `DataB` | |
main :: IO () | |
main = do | |
db <- businessLogic 1 | |
print db |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Based on a discussion where we had a hard to troubleshoot bug.
The goal with the above code is to make sure that the functions are wired up properly. In this case
DataB
starts in theUnbuilt
case via themkDataB
smart constructor. The only way to convert it to aDataB Built
is via thebuiltDataB
function, and must take aQueried
type so as to not receive an arbitrarily initialized value and defeat the purpose of all this.