fcf-containers-0.8.2: examples/Crabcombat.hs
{-# 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 20 d22](https://adventofcode.com/2020/day/22). We use the described
example input data in a bit different form. See the link for the
original data and answer.
We implement the following algorithm on type-level:
>>> playGame2h :: [([Int],[Int])] -> ([Int],[Int]) -> ([Int],Player)
>>> playGame2h _ (p1,[]) = (p1,P1)
>>> playGame2h _ ([],p2) = (p2,P2)
>>> playGame2h hist hs@(v1:r1,v2:r2) =
>>> if elem hs hist
>>> then (r1 ++ [v1,v2], P1)
>>> else
>>> if length r1 >= v1 && length r2 >= v2
>>> then
>>> case playGame2h [] (take v1 r1, take v2 r2) of
>>> (_,P1) -> playGame2h (hs : hist) (r1 ++ [v1,v2], r2)
>>> (_,P2) -> playGame2h (hs : hist) (r1, r2 ++ [v2,v1])
>>> else
>>> if v1 > v2
>>> then playGame2h (hs : hist) (r1 ++ [v1,v2], r2)
>>> else playGame2h (hs : hist) (r1, r2 ++ [v2,v1])
>>>
>>> day22h :: [String] -> Int
>>> day22h strs =
>>> let (p,_) = playGame2h [] hnds -- 62 min if using <> and 7 s if ++ for list cat
>>> len = length p
>>> is = [1..len]
>>> in foldl (+) 0 $ zipWith (*) is $ reverse $ p
>>> where
>>> hnds = input2handsf strs
-}
--------------------------------------------------------------------------------
import GHC.TypeLits (natVal)
import Data.Proxy
import Fcf (Eval, If, Exp, TyEq, Pure, type (&&), type (<=<), type (=<<)
, Uncurry, Bimap, Fst)
import Fcf.Alg.List (MToN)
import Fcf.Data.Nat as N
import Fcf.Data.List (Take, type (++), Elem, Length, Foldr, ZipWith, Reverse)
--------------------------------------------------------------------------------
-- | Example input for which the answer is 291.
type ExampleInput = '( '[9, 2, 6, 3, 1], '[5, 8, 4, 7, 10]) -- :: ([Nat],[Nat])
data Player = P1 | P2
-- :kind! Eval (PlayGame '[] ExampleInput)
data PlayGame :: [([Nat],[Nat])] -> ([Nat],[Nat]) -> Exp ([Nat],Player)
type instance Eval (PlayGame _ '( p1 ': ps , '[])) = '( p1 ': ps, 'P1)
type instance Eval (PlayGame _ '( '[], p2 ': ps)) = '( p2 ': ps , 'P2)
type instance Eval (PlayGame hist '( v1 ': r1, v2 ': r2)) = Eval
(If (Eval (Elem '(v1 ': r1, v2 ': r2) hist ))
(Pure '( Eval (r1 ++ '[v1,v2]), 'P1) )
(If ( Eval (Eval (v1 <= Eval (Length r1)) && Eval (v2 <= Eval (Length r2)) ))
(If (Eval ( TyEq (Eval (PlayGame '[] '( Eval (Take v1 r1), Eval (Take v2 r2)) ) ) 'P1))
(PlayGame ('( v1 ': r1, v2 ': r2) ': hist) '(Eval (r1 ++ '[v1,v2]), r2))
(PlayGame ('( v1 ': r1, v2 ': r2) ': hist) '(r1, Eval (r2 ++ '[v2,v1])))
)
(If (Eval (v1 > v2))
(PlayGame ('( v1 ': r1, v2 ': r2) ': hist) '(Eval (r1 ++ '[v1,v2]), r2))
(PlayGame ('( v1 ': r1, v2 ': r2) ': hist) '(r1, Eval (r2 ++ '[v2,v1])))
)
)
)
-- | Helper.
data AsPair :: a -> Exp (a,a)
type instance Eval (AsPair a) = '(a,a)
-- | Calculating the result from the winners hand.
--
-- :kind! Eval (Day22 ExampleInput)
data Day22 :: ([Nat],[Nat]) -> Exp Nat
type instance Eval (Day22 input) = Eval
(
Foldr (N.+) 0
=<< Uncurry (ZipWith (N.*))
=<< Bimap (MToN 1 <=< Length) Reverse
=<< AsPair
=<< Fst
=<< PlayGame '[] input
)
-- | The actual larger input. The term-level implementation time consumption
-- is very sensitive to the compiler being able to optimize it. (E.g. use tail
-- recursion?). Interesting to see, how ghc is doing this on type-level.
--
-- Does not compile on my machine (not even with the -freduction-depth=0 at cabal),
-- not enough memory
-- Ghci also gets 'Killed'.
{-
type Input =
'( '[21, 50, 9, 45, 16, 47, 27, 38, 29, 48, 10, 42, 32, 31, 41, 11, 8, 33, 25, 30, 12, 40, 7, 23, 46]
, '[22, 20, 44, 2, 26, 17, 34, 37, 43, 5, 15, 18, 36, 19, 24, 35, 3, 13, 14, 1, 6, 39, 49, 4, 28]
) -- :: ([Nat], [Nat])
-- On my machine, the compilation takes about 3 minutes
type Input2 =
'( '[21, 50, 9, 45, 16, 47, 27, 38, 29, 48, 10, 7, 23, 46]
, '[22, 20, 44, 2, 26, 17, 34, 37, 43, 5, 15, 49, 4, 28]
) -- :: ([Nat], [Nat])
-- On my machine, does not compilate, not enough memory.
type Input3 =
'( '[21, 50, 9, 45, 16, 47, 27, 38, 29, 48, 10, 12, 40, 7, 23, 46]
, '[22, 20, 44, 2, 26, 17, 34, 37, 43, 5, 15, 6, 39, 49, 4, 28]
) -- :: ([Nat], [Nat])
-- On my machine, does not compilate, not enough memory.
type Input4 =
'( '[21, 50, 9, 45, 16, 47, 27, 38, 29, 48, 10, 40, 7, 23, 46]
, '[22, 20, 44, 2, 26, 17, 34, 37, 43, 5, 15, 39, 49, 4, 28]
) -- :: ([Nat], [Nat])
-}
--------------------------------------------------------------------------------
-- | Bring the result to term-level.
showExampleInputResult :: forall result. (result ~ Eval (Day22 ExampleInput)) => String
showExampleInputResult = show $ natVal @result Proxy
-- showResult :: forall result. (result ~ Eval (Day22 Input2)) => String
-- showResult :: forall result. (result ~ Eval (Day22 Input3)) => String
-- showResult :: forall result. (result ~ Eval (Day22 Input4)) => String
-- showResult = show $ natVal @result Proxy
main :: IO ()
main = putStrLn $ "The result is:\n" ++ showExampleInputResult
-- main = putStrLn $ "The result is:\n" ++ showResult