{-# LANGUAGE DataKinds #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeInType #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wall #-} {-# OPTIONS_GHC -Werror=incomplete-patterns #-} {-| Example on how to do compile-time (ie type-level) computations and how to get the results into use on term-level (ie runtime). The original problem is described in [aoc 19 d6](https://adventofcode.com/2019/day/6). We use the described example input data in a bit different form. See the link for the original data and answer. -} -------------------------------------------------------------------------------- import qualified GHC.TypeLits as TL import Data.Proxy import Fcf import Fcf.Data.Nat import Fcf.Data.List as L import Fcf.Alg.Morphism import Fcf.Alg.List import Fcf.Alg.Tree -------------------------------------------------------------------------------- -- | Type-level variable containing all the required information. data OrbitInput :: Exp [(TL.Symbol, TL.Symbol)] type instance Eval OrbitInput = '[ '("COM", "B") , '("B", "C") , '("C", "D") , '("D", "E") , '("E", "F") , '("B", "G") , '("G", "H") , '("D", "I") , '("E", "J") , '("J", "K") , '("K", "L")] -- helper, we might could do without this one -- This is used to check that two symbols are same. data EqPlanet :: TL.Symbol -> TL.Symbol -> Exp Bool type instance Eval (EqPlanet a b) = Eval (TyEq 'EQ (TL.CmpSymbol a b)) -- | :kind! Eval (FindDescs "COM" =<< OrbitInput) data FindDescs :: TL.Symbol -> [(TL.Symbol, TL.Symbol)] -> Exp [TL.Symbol] type instance Eval (FindDescs p lst) = Eval (Map Snd =<< (Filter (EqPlanet p <=< Fst) lst)) -- | This is not used in the final solution but worked as a step towards it. -- -- :kind! Eval (Ana BuildOrbTreeCoA "COM") data BuildOrbTreeCoA :: CoAlgebra (TreeF TL.Symbol) TL.Symbol type instance Eval (BuildOrbTreeCoA nd) = If (Eval (L.Null (Eval (FindDescs nd =<< OrbitInput)))) ('NodeF nd '[]) ('NodeF nd (Eval (FindDescs nd =<< OrbitInput))) -- | -- We need to store the depth of the node along the tree. We use the depth when -- counting paths from root to every node. -- -- :kind! Eval (Ana BuildOrbTreeCoA2 '(0,"COM")) data BuildOrbTreeCoA2 :: CoAlgebra (TreeF (Nat,TL.Symbol)) (Nat,TL.Symbol) type instance Eval (BuildOrbTreeCoA2 '(d,nd)) = If (Eval (L.Null (Eval (FindDescs nd =<< OrbitInput)))) ('NodeF '(d,nd) '[]) ('NodeF '(d,nd) (Eval (MkPairLst (1 TL.+ d) =<< FindDescs nd =<< OrbitInput))) -- helper, we might could do without this one data MkPair :: Nat -> TL.Symbol -> Exp (Nat,TL.Symbol) type instance Eval (MkPair n s) = '(n,s) -- helper, we might could do without this one data MkPairLst :: Nat -> [TL.Symbol] -> Exp [(Nat,TL.Symbol)] type instance Eval (MkPairLst n lst) = Eval (Map (MkPair n) lst) -- | Count paths from root to every node. -- -- If it is a leaf, the number of paths from root to it is the depth. -- If it is an internal node, the number of paths to the subtree rooted -- by it is sum of pathnums of subtrees plus the depth (paths to this one). data CountPathsAlg :: Algebra (TreeF (Nat,TL.Symbol)) Nat type instance Eval (CountPathsAlg ('NodeF '(d,_) '[])) = d type instance Eval (CountPathsAlg ('NodeF '(d,_) (b ': bs))) = d TL.+ (Eval (Sum (b ': bs))) -- | We initialize this with the known starting node and the depth of it, which -- is 0. -- -- :kind! Eval OrbitSolution data OrbitSolution :: Exp Nat type instance Eval OrbitSolution = Eval (Hylo CountPathsAlg BuildOrbTreeCoA2 '(0,"COM")) -- | Helper to bring in the result of the type-level computation. -- -- It is a nice exercise to solve this using Tree, UnfoldTree and FoldTree. showResult :: forall n. (n ~ Eval OrbitSolution) => String showResult = show $ TL.natVal @n Proxy main :: IO () main = putStrLn $ "Number of orbits is " ++ showResult