imp-ppl-0.1.0.0: src/Imp/DSL/Combinators.hs
{-# LANGUAGE AllowAmbiguousTypes, UndecidableInstances #-}
-- | Iteration combinators over generated Knightian names.
module Imp.DSL.Combinators
( GenNames
, ConcatMapTag
, MapName(..)
, intervalMap
, knightMap
, intervalN
, knightN
, TagFoldM(..)
, foldMN
, tagFold
, tagMap
, tagScan
, tagN
, foldN
, scanN
) where
import GHC.TypeLits
( Natural, Symbol, AppendSymbol, ConsSymbol, NatToChar, KnownSymbol
, Div, Mod, type (+), type (-) )
import Prelude hiding (return, (>>=), (>>), flip, fmap)
import Data.Proxy (Proxy(..))
import Imp.DSL.Grade (Merge, TagAll)
import Imp.DSL (Imp(..), knight, interval)
-- | Convert a type-level 'Natural' to its decimal 'Symbol' representation.
type family NatToSymbol (n :: Natural) :: Symbol where
NatToSymbol n = NatToSymbolGo (Div n 10) (Mod n 10)
type family NatToSymbolGo (q :: Natural) (r :: Natural) :: Symbol where
NatToSymbolGo 0 r = ConsSymbol (NatToChar (r + 48)) ""
NatToSymbolGo q r = AppendSymbol (NatToSymbol q) (ConsSymbol (NatToChar (r + 48)) "")
-- | Number of decimal digits in a 'Natural'.
type family Digits (n :: Natural) :: Natural where
Digits n = DigitsH (Div n 10)
type family DigitsH (q :: Natural) :: Natural where
DigitsH 0 = 1
DigitsH q = 1 + DigitsH (Div q 10)
-- | @k@ zeros: @Zeros 3 = \"000\"@.
type family Zeros (k :: Natural) :: Symbol where
Zeros 0 = ""
Zeros k = AppendSymbol "0" (Zeros (k - 1))
-- | Decimal representation of @n@, left-padded with zeros to @width@ digits.
type PadNat (width :: Natural) (n :: Natural) =
AppendSymbol (Zeros (width - Digits n)) (NatToSymbol n)
-- | Generate a list of 1-indexed numbered names.
-- Padding matters because grades are kept sorted lexicographically.
type family GenNames (count :: Natural) (base :: Symbol) :: [Symbol] where
GenNames 0 _ = '[]
GenNames n base = GenNamesGo n (Digits n) base
type family GenNamesGo (n :: Natural) (width :: Natural) (base :: Symbol) :: [Symbol] where
GenNamesGo 0 _ _ = '[]
GenNamesGo n width base =
Merge (GenNamesGo (n - 1) width base) '[AppendSymbol base (PadNat width n)]
-- | Map 'TagAll' over a list of tags and concatenate the results.
type family ConcatMapTag (tags :: [Symbol]) (k :: [Symbol]) :: [Symbol] where
ConcatMapTag '[] _ = '[]
ConcatMapTag (t ': ts) k = Merge (TagAll t k) (ConcatMapTag ts k)
-- | Graded @map@: traverse a type-level list of Knightian names,
-- applying a single-name computation to each.
--
-- @
-- mapName \@'[\"x\", \"y\"] knight -- two independent Knightian choices
-- @
class MapName (names :: [Symbol]) where
mapName :: Ord a => (forall n. KnownSymbol n => Imp '[n] a) -> Imp names [a]
instance MapName '[] where
mapName _ = ImpReturn []
instance (KnownSymbol n, MapName ns, Merge '[n] ns ~ (n ': ns)) => MapName (n ': ns) where
mapName f = ImpBind (f @n) $ \x ->
ImpBind (mapName @ns f) (ImpReturn . (x :))
-- | Independent Knightian choice per name.
knightMap :: MapName names => Imp names [Bool]
knightMap = mapName knight
-- | Independent interval per name.
intervalMap :: MapName names => Double -> Double -> Imp names [Bool]
intervalMap lo hi = mapName (interval lo hi)
-- | Independent numbered Knightian choices.
knightN :: forall n base.
MapName (GenNames n base) =>
Imp (GenNames n base) [Bool]
knightN = knightMap @(GenNames n base)
-- | Independent numbered intervals.
intervalN :: forall n base.
MapName (GenNames n base) =>
Double -> Double -> Imp (GenNames n base) [Bool]
intervalN lo hi = intervalMap @(GenNames n base) lo hi
-- | Graded left fold where the step function is monadic with unit grade.
-- Enables conditioning/flips at each time step.
--
-- @
-- tagFoldM \@'[\"move1\", \"move2\"] robotDynamics P1 $ \\pos move -> Imp.do
-- let pos' = step3 pos move
-- observe (pos' /= P0)
-- Imp.return pos'
-- @
class TagFoldM (tags :: [Symbol]) (g :: [Symbol]) where
tagFoldM :: (Ord a, Ord b)
=> Imp g a -> b -> (b -> a -> Imp '[] b) -> Imp (ConcatMapTag tags g) b
instance TagFoldM '[] g where
tagFoldM _ acc _ = ImpReturn acc
instance ( KnownSymbol t
, TagFoldM ts g
) => TagFoldM (t ': ts) g where
tagFoldM prog acc f = ImpBind (ImpTag (Proxy @t) prog) $ \x ->
ImpBind (f acc x) $ \acc' ->
tagFoldM @ts @g prog acc' f
-- | @foldMN \@n \@base prog acc f@: numbered monadic fold.
foldMN :: forall n base g a b.
(TagFoldM (GenNames n base) g, Ord a, Ord b) =>
Imp g a -> b -> (b -> a -> Imp '[] b) -> Imp (ConcatMapTag (GenNames n base) g) b
foldMN = tagFoldM @(GenNames n base) @g
-- | Graded left fold over tagged subprograms.
--
-- @
-- tagFold \@'[\"move1\", \"move2\"] robotDynamics P0 step3
-- @
tagFold :: forall tags g a b. (TagFoldM tags g, Ord a, Ord b) =>
Imp g a -> b -> (b -> a -> b) -> Imp (ConcatMapTag tags g) b
tagFold prog acc f = tagFoldM @tags prog acc (\b a -> ImpReturn (f b a))
-- | Graded @map@ over tagged subprograms.
--
-- @
-- tagMap \@'[\"move1\", \"move2\", \"move3\"] robotDynamics
-- @
tagMap :: forall tags g a. (TagFoldM tags g, Ord a)
=> Imp g a -> Imp (ConcatMapTag tags g) [a]
tagMap prog = ImpBind (tagFold @tags prog [] (\as a -> a : as)) (ImpReturn . reverse)
-- | Graded left scan over tagged subprograms.
--
-- @
-- tagScan \@'[\"move1\", \"move2\", \"move3\"] robotDynamics P1 step3
-- @
tagScan :: forall tags g a b. (TagFoldM tags g, Ord a, Ord b) =>
Imp g a -> b -> (b -> a -> b) -> Imp (ConcatMapTag tags g) [b]
tagScan prog acc f =
ImpBind (tagFold @tags prog (acc, []) (\(b, bs) a -> let b' = f b a in (b', b' : bs)))
(ImpReturn . reverse . snd)
-- | @tagN \@n \@base prog@: numbered tag iteration.
tagN :: forall n base g a.
(TagFoldM (GenNames n base) g, Ord a) =>
Imp g a -> Imp (ConcatMapTag (GenNames n base) g) [a]
tagN = tagMap @(GenNames n base)
-- | @foldN \@n \@base prog acc f@: numbered fold over tagged subprograms.
foldN :: forall n base g a b.
(TagFoldM (GenNames n base) g, Ord a, Ord b) =>
Imp g a -> b -> (b -> a -> b) -> Imp (ConcatMapTag (GenNames n base) g) b
foldN = tagFold @(GenNames n base) @g
-- | @scanN \@n \@base prog acc f@: numbered scan over tagged subprograms.
scanN :: forall n base g a b.
(TagFoldM (GenNames n base) g, Ord a, Ord b) =>
Imp g a -> b -> (b -> a -> b) -> Imp (ConcatMapTag (GenNames n base) g) [b]
scanN = tagScan @(GenNames n base) @g