{-# LANGUAGE GADTs #-}
{-# LANGUAGE LinearTypes #-}
{-# LANGUAGE QualifiedDo #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Main (main) where
import qualified Control.Functor.Linear as Control
import qualified Control.Functor.Linear as Linear
import Control.Monad.Free.Linear (FreeL (..), liftF)
import qualified Data.Functor.Linear as Data
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Prelude.Linear hiding ((.))
import qualified Prelude.Linear as L
import Test.Hspec (describe, hspec, it, shouldBe)
import qualified Prelude as Base
data ProductType
= Eggs
| Milk
| Flour
| Dough
| Pancake
deriving (Base.Eq, Base.Ord, Show)
newtype ProductId = ProductId Int
data RecipeF a where
Buy :: ProductType -> (ProductId %1 -> a) %1 -> RecipeF a
Combine :: ProductType -> [ProductId] %1 -> (ProductId %1 -> a) %1 -> RecipeF a
Cook :: ProductType -> ProductId %1 -> (ProductId %1 -> a) %1 -> RecipeF a
instance Data.Functor RecipeF where
fmap f (Buy t c) = Buy t $ f L.. c
fmap f (Combine t cs c) = Combine t cs $ f L.. c
fmap f (Cook t p c) = Cook t p $ f L.. c
instance Control.Functor RecipeF where
fmap f (Buy t c) = Buy t $ f L.. c
fmap f (Combine t cs c) = Combine t cs $ f L.. c
fmap f (Cook t p c) = Cook t p $ f L.. c
type Recipe a = FreeL RecipeF a
buy :: ProductType -> Recipe ProductId
buy t = liftF $ Buy t id
combine :: ProductType -> [ProductId] %1 -> Recipe ProductId
combine t ps = liftF $ Combine t ps id
cook :: ProductType -> ProductId %1 -> Recipe ProductId
cook t p = liftF $ Cook t p id
pancakeRecipe :: Recipe ProductId
pancakeRecipe = Linear.do
eggs <- buy Eggs
milk <- buy Milk
flour <- buy Flour
dough <- combine Dough [eggs, milk, flour]
pancake <- cook Pancake dough
Linear.pure pancake
gatherIngredients :: Recipe a -> Map ProductType Int
gatherIngredients (Pure _) = Map.empty
gatherIngredients (Free x) = case x of
Buy t c -> Map.insertWith (Base.+) t 1 $ gatherIngredients (c productId)
Combine _ _ c -> gatherIngredients $ c productId
Cook _ _ c -> gatherIngredients $ c productId
where
-- Doesn't matter what we put here, it will not be used anyways
productId = ProductId 0
main :: IO ()
main = hspec $ describe "Linear free monad" $ do
describe "pancake recipe example" $ do
it "can gather the ingredients" $ do
gatherIngredients pancakeRecipe `shouldBe` Map.fromList
[ (Eggs, 1)
, (Milk, 1)
, (Flour, 1)
]