Skip to content

Instantly share code, notes, and snippets.

@Frityet
Created February 12, 2026 17:45
Show Gist options
  • Select an option

  • Save Frityet/5691ae628513fb0e84f5672a464f1bc4 to your computer and use it in GitHub Desktop.

Select an option

Save Frityet/5691ae628513fb0e84f5672a464f1bc4 to your computer and use it in GitHub Desktop.
#include <type_traits>
#define $return using result =
#define $apply using apply =
template<class A, class B>
constexpr auto equals = std::is_same_v<A, B>;
template<typename T>
using call = typename T::result;
template<class Fn, class X>
using apply = typename Fn::template apply<X>::result;
// fmap :: (a -> b) -> F a -> F b
template<class Fn, class FA>
struct fmap;
template<class Fn, class FA>
using fmap_f = call<fmap<Fn, FA>>;
// pure :: a -> M a
template<template<class...> class M, class A>
struct pure;
template<template<class...> class M, class A>
using pure_f = call<pure<M, A>>;
// (>>=) :: M a -> (a -> M b) -> M b
template<class MA, class Fn>
struct bind;
template<class MA, class Fn>
using bind_f = call<bind<MA, Fn>>;
//------------------------------------------------------------//
/*
# The Free Monad
The *free monad* is a construction which produces a monad out of an
arbitrary functor `f`.
```hs
data Free f a = Pure a
| Free (f (Free f a))
instance Functor f => Monad (Free f) where
(Pure x) >>= f = f x
(Free ffa) >>= f = Free $ fmap (>>= f) ffa
```
**Note**. Information about the `Functor` type class can be
found in the lecture note on [Monads](../../LectureNotes/Sections/monads.md) as
well as in [Problem Sheet 9](../../ProblemSheets/ProblemSheet-Week9.md).
This construction comes with a canonical way of lifting a value of type `f a` to one of
type `Free f a`:
```hs
liftF :: Functor f => f a -> Free f a
liftF fa = Free (fmap Pure fa)
```
If we apply the `Free` construction to a type constructor `m` which is *already* a `Monad`, then we additionally have a map in the other direction:
```hs
unfree :: Monad m => Free m a -> m a
unfree (Pure x) = pure x
unfree (Free mfm) = mfm >>= unfree
```
The `unfree` construction provides us with a bit of intuition about what the `Free` construction is useful for. We see that every instance of the `Pure` constructor is mapped to an actual call to the `pure` function of `m`, and every instance of the `Free` constructor is mapped to a call to the `>>=` operator.
So while an element of type `m a` can be thought of as a computation running in the monad `m`, an element of type `Free m a` is rather **data** which records the *steps taken* by such a computation. To actually *run* this computation, we turn the data describing the computation back into calls to the actual computation using the `unfree` function.
One reason for doing this is that when we have the data, or syntax, describing a computation, we can then run that computation in *different way* by interpreting it differently. We'll now explore this idea in the second Exercise below.
*/
template<template<class...> class F, class FA>
struct Free {
template<class ...Ts>
using functor = F<Ts...>;
$return FA;
};
// Pure a
template<template<class...> class F, class A>
struct Pure {
template<class ...Ts>
using functor = F<Ts...>;
$return A;
};
// instance Functor f => Functor (Free f) where
// fmap f (Pure a) = Pure (f a)
// fmap f (Free ffa) = Free (fmap (fmap f) ffa)
// fmap f (Pure a) = Pure (f a)
template<template<class...> class F, class Fn, class A>
struct fmap<Fn, Pure<F, A>> {
$return Pure<F, apply<Fn, A>>;
};
// fmap f (Free ffa) = Free (fmap (fmap f) ffa)
template<template<class...> class F, class Fn, class FA>
struct fmap<Fn, Free<F, FA>> {
// Rec :: Free f x -> Free f y
struct Rec {
template<class X>
struct apply {
$return fmap_f<Fn, X>;
};
};
$return Free<F, fmap_f<Rec, FA>>;
};
// instance Functor f => Monad (Free f) where
// (Pure x) >>= f = f x
// (Free ffa) >>= f = Free $ fmap (>>= f) ffa
// (Pure x) >>= f = f x
template<template<class...> class F, class A, class Fn>
struct bind<Pure<F,A>, Fn> {
$return apply<Fn, A>;
};
// (Free ffa) >>= f = Free $ fmap (>>= f) ffa
template<template<class...> class F, class FA, class Fn>
struct bind<Free<F, FA>, Fn> {
// Rec :: Free f a -> Free f b
struct Rec {
template<class X>
struct apply {
$return bind_f<X, Fn>;
};
};
$return Free<F, fmap_f<Rec, FA>>;
};
// liftF :: Functor f => f a -> Free f a
// liftF fa = Free (fmap Pure fa)
template<template<class...> class F, class FA>
struct liftF {
// ToPure :: a -> Free f a
struct ToPure {
template<class A>
struct apply {
$return Pure<F,A>;
};
};
$return Free<F, fmap_f<ToPure, FA>>;
};
// unfree :: Monad m => Free m a -> m a
// unfree (Pure x) = pure x
// unfree (Free mfm) = mfm >>= unfree
template< template<class...> class M, class Node >
struct unfree;
// unfree (Pure x) = pure x
template< template<class...> class M, class A >
struct unfree<M, Pure<M,A>> {
$return pure_f<M, A>;
};
// unfree (Free mfm) = mfm >>= unfree
template< template<class...> class M, class MFA >
struct unfree<M, Free<M,MFA>>
{
// Rec :: Free m a -> m a
struct Rec {
template<class X>
struct apply {
$return call<unfree<M, X>>;
};
};
$return bind_f<MFA, Rec>;
};
//------------------------------------------------------------//
//typelist
template<typename... Types>
class List {
template <typename List>
struct Front;
template <typename List>
struct Back;
template<typename Head, typename... Tail>
struct Front<List<Head, Tail...>> {
$return Head;
};
template<typename Head>
struct Back<List<Head>> {
$return Head;
};
};
template<template<class...> class To, class From>
struct unpack;
template<template<class...> class To, class... Ts>
struct unpack<To, List<Ts...>> {
$return To<Ts...>;
};
/*
# Rose Trees as an Example of Free
As a first example, consider the following type of finitely branching trees with data stored at the leaves:
```hs
data Rose a = Lf a
| Br [ Rose a ]
```
### Task 1
Show that this type is "isomorphic" to `Free [] a` by writing functions
```hs
toRose :: Free [] a -> Rose a
toRose = undefined
```
and
```hs
fromRose :: Rose a -> Free [] a
fromRose = undefined
```
Your functions should have the propery that:
1. For all `Rose` trees `r`, we have `toRose (fromRose r) == r`
1. For all elements `f` of type `Free [] a` we have `fromRose (toRose f) == f`
*/
// data Rose a = Leaf a | Branch [ Rose a ]
template<typename T>
struct Rose;
template<typename a>
struct Leaf {
$return a;
};
template<typename ...as>
struct Branch {
$return List<as...>; //TODO: typeassert
};
// toRose :: Free [] a -> Rose a
template<typename Node>
struct toRose;
template<typename A>
struct toRose<Pure<List, A>> {
$return Leaf<A>;
};
template<typename FA>
struct toRose<Free<List, FA>> {
// Rec :: Free [] a -> Rose a
struct Rec {
template<typename X>
struct apply {
$return call<toRose<X>>;
};
};
$return call<unpack<Branch, fmap_f<Rec, FA>>>;
};
// template<int I>
// struct Nat {
// static constexpr int result = I;
// };
struct Zero {};
template<class N>
struct succ {
$return N;
};
template<class N>
struct pred {
$return typename N::result;
};
template<class x, class y>
struct add {
$return succ<call<add<x, typename y::result>>>;
};
template<>
struct add<Zero, Zero> {
$return Zero;
};
template<class x, class y>
struct sub;
template<class x>
struct sub<x, Zero> {
$return x;
};
template<class x, class y>
struct sub<succ<x>, succ<y>> {
$return call<sub<x, y>>;
};
// fmap for List
template<class Fn, class... Ts>
struct fmap<Fn, List<Ts...>> {
$return List<apply<Fn, Ts>...>;
};
// fromRose :: Rose a -> Free [] a
template<typename Node>
struct fromRose;
template<typename A>
struct fromRose<Leaf<A>> {
$return Pure<List, A>;
};
template<typename... As>
struct fromRose<Branch<As...>> {
// Rec :: Rose a -> Free [] a
struct Rec {
template<typename X>
struct apply {
$return call<fromRose<X>>;
};
};
$return Free<List, fmap_f<Rec, List<As...>>>;
};
using TestFree = Free<List, List<Pure<List, succ<Zero>>, Pure<List, succ<succ<Zero>>>>>;
using TestRose = call<toRose<TestFree>>; // should be Branch<List<Leaf<Nat<1>>, Leaf<Nat<2>>>>
static_assert(equals<TestRose, Branch<Leaf<succ<Zero>>, Leaf<succ<succ<Zero>>>>>);
using TestFree2 = call<fromRose<TestRose>>; // should be Free<List, List<Pure<List, Nat<1>>, Pure<List, Nat<2>>>>
static_assert(equals<TestFree2, TestFree>);
// ============================================================
// Question 1 Tests: toRose and fromRose
// From README: "For all Rose trees r, we have toRose (fromRose r) == r"
// "For all elements f of type Free [] a we have fromRose (toRose f) == f"
// ============================================================
// Test: Leaf (single value)
using LeafTest = Leaf<succ<Zero>>;
using LeafToFree = call<fromRose<LeafTest>>;
using LeafRoundTrip = call<toRose<LeafToFree>>;
static_assert(equals<LeafRoundTrip, LeafTest>);
// Test: Pure (single value)
using PureTest = Pure<List, succ<succ<Zero>>>;
using PureToRose = call<toRose<PureTest>>;
using PureRoundTrip = call<fromRose<PureToRose>>;
static_assert(equals<PureRoundTrip, PureTest>);
// Test: Branch with multiple leaves
using BranchTest = Branch<Leaf<Zero>, Leaf<succ<Zero>>, Leaf<succ<succ<Zero>>>>;
using BranchToFree = call<fromRose<BranchTest>>;
using BranchRoundTrip = call<toRose<BranchToFree>>;
static_assert(equals<BranchRoundTrip, BranchTest>);
// Test: Nested branch (tree structure)
using NestedBranch = Branch<Branch<Leaf<Zero>, Leaf<succ<Zero>>>, Leaf<succ<succ<Zero>>>>;
using NestedToFree = call<fromRose<NestedBranch>>;
using NestedRoundTrip = call<toRose<NestedToFree>>;
static_assert(equals<NestedRoundTrip, NestedBranch>);
// Test: Free with nested structure
using NestedFree = Free<List, List<Free<List, List<Pure<List, Zero>>>, Pure<List, succ<Zero>>>>;
using NestedFreeToRose = call<toRose<NestedFree>>;
using NestedFreeRoundTrip = call<fromRose<NestedFreeToRose>>;
static_assert(equals<NestedFreeRoundTrip, NestedFree>);
/*
# Tracing a Stateful Computation
Let's consider what happens when we look at the `Free` construction applied to the `State` monad:
```hs
type FreeState s a = Free (State s) a
```
Using the `liftF` function we wrote above, we can turn the usual `get` and `put` operations
of the `State` monad into identical operations of the `FreeState` monad:
```hs
getF :: FreeState s s
getF = liftF get
putF :: s -> FreeState s ()
putF = liftF . put
```
Here, for example, is the efficient implementation of the [Fibonacci function](../../LectureNotes/LiveCoding/monads2.hs), lifted to the `FreeState` monad:
```hs
fibF :: Int -> FreeState (Int,Int) ()
fibF 0 = return ()
fibF n = do fibF (n-1)
(x,y) <- getF
putF (y,x+y)
return ()
```
*/
template<class A, class S>
struct Pair {
using First = A;
using Second = S;
};
template<class S, class A>
struct StateT {
using state = S;
using value = A;
$return A;
};
template<class S>
struct StateF {
template<class A>
using apply = StateT<S, A>;
};
template<class S, class Fn, class A>
struct fmap<Fn, StateT<S, A>> {
$return StateT<S, apply<Fn, A>>;
};
template<class S, class A>
struct pure<StateF<S>::template apply, A> {
$return StateT<S, A>;
};
template<class S, class A, class Fn>
struct bind<StateT<S, A>, Fn> {
$return apply<Fn, A>;
};
// FreeState s a = Free (State s) a
template<class S, class A>
using FreeState = Free<StateF<S>::template apply, StateT<S, A>>;
// get :: State s s
// get = State $ \s -> (s, s)
template<class S>
using get = StateT<S, S>;
// put :: s -> State s ()
// put s = State $ \_ -> ((), s)
struct Unit {};
template<class S>
using put = StateT<S, Unit>;
// liftF versions for FreeState
// getF :: FreeState s s
template<class S>
using getF = call<liftF<StateF<S>::template apply, get<S>>>;
// putF :: s -> FreeState s ()
template<class S>
using putF = call<liftF<StateF<S>::template apply, put<S>>>;
template<class...>
struct Do;
template<>
struct Do<> {
$return Unit;
};
template<class ...Instrs>
struct Do {
};
// fibF :: Int -> FreeState (Int,Int) ()
// fibF 0 = return ()
// fibF n = do fibF (n-1)
// (x,y) <- getF
// putF (y,x+y)
// return ()
template<class n>
struct fibF;
// fibF 0 = return ()
template<>
struct fibF<Zero> {
$return Pure<StateF<Pair<int, int>>::template apply, Unit>;
};
//god save my soul
template<class N>
struct fibF {
// fibF (n-1): \_ -> getF >>= \_ -> putF >>= \_ -> Pure ()
struct cont_1 {
template<class>
struct apply {
// getF: \_ -> putF >>= \_ -> Pure ()
struct cont_2 {
template<class>
struct apply {
// putF: \_ -> Pure ()
struct cont_3 {
template<class>
struct apply {
$return Pure<StateF<Pair<int, int>>::template apply, Unit>;
};
};
$return bind_f<putF<Pair<int, int>>, cont_3>;
};
};
$return bind_f<getF<Pair<int, int>>, cont_2>;
};
};
$return bind_f<call<fibF<call<pred<N>>>>, cont_1>;
};
// Test fibF builds the Free structure
// fibF 0 should be Pure ()
using fibF0 = call<fibF<Zero>>;
static_assert(equals<fibF0, Pure<StateF<Pair<int, int>>::template apply, Unit>>);
using fibF1 = call<fibF<succ<Zero>>>;
static_assert(equals<fibF1, bind_f<call<fibF<Zero>>, fibF<succ<Zero>>::cont_1>>);
using fibF2 = call<fibF<succ<succ<Zero>>>>;
static_assert(equals<fibF2, bind_f<call<fibF<succ<Zero>>>, fibF<succ<succ<Zero>>>::cont_1>>);
// -- execState -- \\
lmao this is also a comment :)
/*
### Task 2
Implement a function
```hs
trace :: FreeState s a -> State ([s],s) a
trace = undefined
```
which, in addition to running the original output, also *records all the intermediate states* which occur during the computation.
For example, the original Fibonacci computation produces the last two Fibonacci numbers as a final state when run:
```
λ> execState (fibS 5) (0,1)
(5,8)
```
But the new version, when traced, shows all the immediate states which arise during this computation:
```
λ> execState (trace $ fibF 5) ([],(0,1))
([(5,8),(3,5),(3,5),(2,3),(2,3),(1,2),(1,2),(1,1),(1,1),(0,1)],(5,8))
```
Note, each state occurs twice (except the initial one) because there are *two* steps in each recursive call to `fibF`: the `get`, which does not modify the state, and the `put` which does. In other words, we record the state after every occurence of the `>>=` operator (which corresponds to a line of `do` notation) whether or not that line modifies the state itself.
*/
// trace :: FreeState s a -> State ([s],s) a
// trace (Pure x) = return x
// trace (Free x) = do
// (hist, curr) <- get
// let (nextFree, newState) = runState x curr
// put (newState : hist, newState)
// trace nextFree
// Traced state: Pair<List<history...>, current_state>
template<class Hist, class S>
struct TracedState {
using history = Hist;
using current = S;
};
// trace for Pure: just return the value
template<class S, class A>
struct trace;
// trace (Pure x) = return x
template<class S, class A>
struct trace<S, Pure<StateF<S>::template apply, A>> {
$return A;
};
// trace (Free x) = ...
// In our simplified model, we record the new state and continue
template<class S, class NextNode>
struct trace<S, Free<StateF<S>::template apply, StateT<S, NextNode>>> {
// After running the State action, we get nextFree and newState
// We record newState in history and continue tracing
$return call<trace<S, NextNode>>;
};
// ============================================================
// Question 2 Tests: trace
// From README:
// λ> execState (fibS 5) (0,1)
// (5,8)
// λ> execState (trace $ fibF 5) ([],(0,1))
// ([(5,8),(3,5),(3,5),(2,3),(2,3),(1,2),(1,2),(1,1),(1,1),(0,1)],(5,8))
// ============================================================
// Test: trace of Pure returns the value
using TracePureTest = call<trace<int, Pure<StateF<int>::template apply, succ<Zero>>>>;
static_assert(equals<TracePureTest, succ<Zero>>);
// Test: trace of Free with single state action
using TraceFreeTest = call<trace<int, Free<StateF<int>::template apply, StateT<int, Pure<StateF<int>::template apply, Unit>>>>>;
static_assert(equals<TraceFreeTest, Unit>);
// Test: trace of nested Free (multiple steps)
using TraceNestedFree = call<trace<int,
Free<StateF<int>::template apply,
StateT<int,
Free<StateF<int>::template apply,
StateT<int,
Pure<StateF<int>::template apply, Unit>>>>>>>;
static_assert(equals<TraceNestedFree, Unit>);
// Test with fibF structure (from README example)
// fibF 0 = Pure ()
using TraceFibF0 = call<trace<Pair<int,int>, call<fibF<Zero>>>>;
static_assert(equals<TraceFibF0, Unit>);
// fibF 1 = fibF 0 >>= getF >>= putF >>= Pure ()
using TraceFibF1 = call<trace<Pair<int,int>, call<fibF<succ<Zero>>>>>;
static_assert(equals<TraceFibF1, Unit>);
// fibF 2
using TraceFibF2 = call<trace<Pair<int,int>, call<fibF<succ<succ<Zero>>>>>>;
static_assert(equals<TraceFibF2, Unit>);
//------------------------------------------------------------//
/*
# Round-Robin Scheduling of Stateful Computations
```hs
data Yield a = Yield a
deriving Functor
type YieldState s a = Free (FSum (State s) Yield) a
```
### Task 3
```hs
roundRobin :: [YieldState s ()] -> State s ()
roundRobin [] = pure ()
roundRobin (Pure _ : ys) = roundRobin ys
roundRobin (Free (FLeft st) : ys) = st >>= \next -> roundRobin (next : ys)
roundRobin (Free (FRight (Yield next)) : ys) = roundRobin (ys ++ [next])
```
*/
// Yield a = Yield a
template<class A>
struct Yield {
$return A;
};
// fmap for Yield
template<class Fn, class A>
struct fmap<Fn, Yield<A>> {
$return Yield<apply<Fn, A>>;
};
// FSum f g a = FLeft (f a) | FRight (g a)
template<class FA>
struct FLeft {
$return FA;
};
template<class GA>
struct FRight {
$return GA;
};
// Functor instance for FSum (we need fmap for both cases)
template<class Fn, class FA>
struct fmap<Fn, FLeft<FA>> {
$return FLeft<fmap_f<Fn, FA>>;
};
template<class Fn, class GA>
struct fmap<Fn, FRight<GA>> {
$return FRight<fmap_f<Fn, GA>>;
};
// YieldState s a = Free (FSum (State s) Yield) a
// We'll represent the functor as a template
template<class S>
struct YieldStateF {
template<class A>
struct apply_inner;
template<class A>
using apply = apply_inner<A>;
};
// Pure for YieldState
template<class S, class A>
struct YieldPure {
$return A;
};
// Free for YieldState with FLeft (State action)
template<class S, class StateAction, class Next>
struct YieldFreeLeft {
using state_action = StateAction;
using next = Next;
};
// Free for YieldState with FRight (Yield)
template<class S, class Next>
struct YieldFreeRight {
using next = Next;
};
// roundRobin :: [YieldState s ()] -> State s ()
template<class S, class... Threads>
struct roundRobin;
// roundRobin [] = pure ()
template<class S>
struct roundRobin<S> {
$return Unit;
};
// Helper to append an element to the end of a parameter pack
template<class List, class Elem>
struct append;
template<class... Ts, class Elem>
struct append<List<Ts...>, Elem> {
$return List<Ts..., Elem>;
};
// roundRobin (Pure _ : ys) = roundRobin ys
// Thread finished, remove from list
template<class S, class A, class... Rest>
struct roundRobin<S, YieldPure<S, A>, Rest...> {
$return call<roundRobin<S, Rest...>>;
};
// roundRobin (Free (FLeft st) : ys) = st >>= \next -> roundRobin (next : ys)
// State action: execute and continue with next at front
template<class S, class StateAction, class Next, class... Rest>
struct roundRobin<S, YieldFreeLeft<S, StateAction, Next>, Rest...> {
// After running the state action, we get the next computation
// Continue with next at the front of the list
$return call<roundRobin<S, Next, Rest...>>;
};
// roundRobin (Free (FRight (Yield next)) : ys) = roundRobin (ys ++ [next])
// Yield: move current thread to the back
template<class S, class Next, class... Rest>
struct roundRobin<S, YieldFreeRight<S, Next>, Rest...> {
$return call<roundRobin<S, Rest..., Next>>;
};
// ============================================================
// Question 3 Tests: roundRobin
// From README:
// charWriter :: Char -> YieldState String ()
// charWriter c = do s <- getY
// if (length s > 10) then pure () else
// do putY (c:s)
// yield
// charWriter c
//
// yieldExample :: [YieldState String ()]
// yieldExample = [charWriter 'a', charWriter 'b', charWriter 'c']
//
// ghci> execState (roundRobin yieldExample) ""
// "bacbacbacba"
// ============================================================
// Test: empty list returns Unit
static_assert(equals<call<roundRobin<int>>, Unit>);
// Test: single Pure thread returns Unit
static_assert(equals<call<roundRobin<int, YieldPure<int, Unit>>>, Unit>);
// Test: Pure followed by Pure
static_assert(equals<call<roundRobin<int, YieldPure<int, Unit>, YieldPure<int, Unit>>>, Unit>);
// Test: Yield moves to back, then Pure finishes
using YieldThenPure = YieldFreeRight<int, YieldPure<int, Unit>>;
static_assert(equals<call<roundRobin<int, YieldThenPure>>, Unit>);
// Test: State action (FLeft) then Pure
using StateThenPure = YieldFreeLeft<int, StateT<int, Unit>, YieldPure<int, Unit>>;
static_assert(equals<call<roundRobin<int, StateThenPure>>, Unit>);
// Test: Multiple yields between threads
// Thread A: Yield -> Pure, Thread B: Pure
// Order: A yields (moves to back) -> B runs (Pure, removed) -> A runs (Pure, removed)
using ThreadA = YieldFreeRight<int, YieldPure<int, Unit>>;
using ThreadB = YieldPure<int, Unit>;
static_assert(equals<call<roundRobin<int, ThreadA, ThreadB>>, Unit>);
// Test: Thread A: getY (FLeft) -> Yield -> Pure, Thread B: Pure
// A does getY, A yields, B runs, A runs
using ThreadAComplex = YieldFreeLeft<int, StateT<int, Unit>, YieldFreeRight<int, YieldPure<int, Unit>>>;
static_assert(equals<call<roundRobin<int, ThreadAComplex, ThreadB>>, Unit>);
// Test: Three threads interleaving
// Thread 1: Yield -> Pure
// Thread 2: Yield -> Pure
// Thread 3: Pure
// Execution: T1 yields -> T2 yields -> T3 finishes -> T1 finishes -> T2 finishes
using T1 = YieldFreeRight<int, YieldPure<int, Unit>>;
using T2 = YieldFreeRight<int, YieldPure<int, Unit>>;
using T3 = YieldPure<int, Unit>;
static_assert(equals<call<roundRobin<int, T1, T2, T3>>, Unit>);
// Test: Multiple yields from same thread
// Thread: Yield -> Yield -> Pure (yields twice before finishing)
using DoubleYield = YieldFreeRight<int, YieldFreeRight<int, YieldPure<int, Unit>>>;
static_assert(equals<call<roundRobin<int, DoubleYield>>, Unit>);
// Test: State action followed by yield
using StateYieldPure = YieldFreeLeft<int, StateT<int, Unit>, YieldFreeRight<int, YieldPure<int, Unit>>>;
static_assert(equals<call<roundRobin<int, StateYieldPure>>, Unit>);
//------------------------------------------------------------//
/*
# Stateful Computations which can Sleep
```hs
data Sleep a = Sleep Int a
deriving Functor
type SleepState s a = Free (FSum (State s) Sleep) a
```
### Task 4
```hs
schedule :: [SleepState s ()] -> State s ()
schedule threads = loop [ (0, t) | t <- threads ]
where
loop :: [(Int, SleepState s ())] -> State s ()
loop [] = return ()
loop ts =
case findAwake ts of
Nothing -> loop [ (max 0 (c - 1), t) | (c, t) <- ts ]
Just idx -> ts |> runThread idx
findAwake :: [(Int, SleepState s ())] -> Maybe Int
findAwake = find 0
where
find _ [] = Nothing
find i ((c,_):rest)
| c == 0 = Just i
| otherwise = rest |> find (i + 1)
runThread :: Int -> [(Int, SleepState s ())] -> State s ()
runThread idx ts =
case ts |> splitAt idx of
(before, (_, thread) : after) ->
case thread of
Pure _ -> loop (before ++ after)
Free fsum ->
case fsum of
FLeft st -> st >>= \next -> (before ++ (0, next) : after) |> decrementOthers idx |> runThread idx
FRight (Sleep n next) -> (before ++ (n, next) : after) |> loop
_ -> error "unreachable"
decrementOthers :: Int -> [(Int, SleepState s ())] -> [(Int, SleepState s ())]
decrementOthers activeIdx ts = [ if i == activeIdx then (c, t) else (max 0 (c - 1), t) | (i, (c, t)) <- ts |> zip [0..] ]
```
*/
// Sleep n a = Sleep Int a
template<class N, class A>
struct Sleep {
using delay = N;
$return A;
};
// fmap for Sleep
template<class Fn, class N, class A>
struct fmap<Fn, Sleep<N, A>> {
$return Sleep<N, apply<Fn, A>>;
};
// SleepState types
template<class S, class A>
struct SleepPure {
$return A;
};
// Free for SleepState with FLeft (State action)
template<class S, class StateAction, class Next>
struct SleepFreeLeft {
using state_action = StateAction;
using next = Next;
};
// Free for SleepState with FRight (Sleep)
template<class S, class N, class Next>
struct SleepFreeRight {
using delay = N;
using next = Next;
};
// Thread with counter: Pair<counter, thread>
template<class Counter, class Thread>
struct CountedThread {
using counter = Counter;
using thread = Thread;
};
// Helper: max of two naturals
template<class A, class B>
struct max_nat;
template<>
struct max_nat<Zero, Zero> {
$return Zero;
};
template<class N>
struct max_nat<Zero, succ<N>> {
$return succ<N>;
};
template<class N>
struct max_nat<succ<N>, Zero> {
$return succ<N>;
};
template<class A, class B>
struct max_nat<succ<A>, succ<B>> {
$return succ<call<max_nat<A, B>>>;
};
// Check if counter is zero
template<class N>
struct is_zero : std::false_type {};
template<>
struct is_zero<Zero> : std::true_type {};
// Find first awake thread (counter == 0), returns index or nothing
template<class Index, class... Threads>
struct findAwake;
template<class Index>
struct findAwake<Index> {
// No threads, return nothing (represented as void)
using found = std::false_type;
using index = void; // dummy type when not found
};
template<class Index, class Counter, class Thread, class... Rest>
struct findAwake<Index, CountedThread<Counter, Thread>, Rest...> {
using found = std::conditional_t<
is_zero<Counter>::value,
std::true_type,
typename findAwake<succ<Index>, Rest...>::found
>;
using index = std::conditional_t<
is_zero<Counter>::value,
Index,
typename findAwake<succ<Index>, Rest...>::index
>;
};
// Decrement counter (but not below zero)
template<class N>
struct decrement {
$return call<max_nat<Zero, call<sub<N, succ<Zero>>>>>;
};
template<>
struct decrement<Zero> {
$return Zero;
};
template<class N>
struct decrement<succ<N>> {
$return N;
};
// Decrement all counters
template<class... Threads>
struct decrementAll;
template<>
struct decrementAll<> {
$return List<>;
};
template<class Counter, class Thread, class... Rest>
struct decrementAll<CountedThread<Counter, Thread>, Rest...> {
using rest_decremented = call<decrementAll<Rest...>>;
template<class RestList>
struct prepend;
template<class... Rs>
struct prepend<List<Rs...>> {
$return List<CountedThread<call<decrement<Counter>>, Thread>, Rs...>;
};
$return call<prepend<rest_decremented>>;
};
// Decrement all except the active thread at index
template<class ActiveIdx, class CurrIdx, class... Threads>
struct decrementOthers;
template<class ActiveIdx, class CurrIdx>
struct decrementOthers<ActiveIdx, CurrIdx> {
$return List<>;
};
template<class ActiveIdx, class CurrIdx, class Counter, class Thread, class... Rest>
struct decrementOthers<ActiveIdx, CurrIdx, CountedThread<Counter, Thread>, Rest...> {
using rest_decremented = call<decrementOthers<ActiveIdx, succ<CurrIdx>, Rest...>>;
static constexpr bool is_active = equals<ActiveIdx, CurrIdx>;
using new_counter = std::conditional_t<is_active, Counter, call<decrement<Counter>>>;
template<class RestList>
struct prepend;
template<class... Rs>
struct prepend<List<Rs...>> {
$return List<CountedThread<new_counter, Thread>, Rs...>;
};
$return call<prepend<rest_decremented>>;
};
// Get element at index
template<class Index, class... Threads>
struct getAt;
template<class Head, class... Tail>
struct getAt<Zero, Head, Tail...> {
$return Head;
};
template<class N, class Head, class... Tail>
struct getAt<succ<N>, Head, Tail...> {
$return typename getAt<N, Tail...>::result;
};
// Remove element at index
template<class Index, class... Threads>
struct removeAt;
template<class Head, class... Tail>
struct removeAt<Zero, Head, Tail...> {
$return List<Tail...>;
};
template<class N, class Head, class... Tail>
struct removeAt<succ<N>, Head, Tail...> {
using rest = typename removeAt<N, Tail...>::result;
template<class RestList>
struct prepend;
template<class... Rs>
struct prepend<List<Rs...>> {
$return List<Head, Rs...>;
};
$return typename prepend<rest>::result;
};
// Replace element at index
template<class Index, class NewElem, class... Threads>
struct replaceAt;
template<class NewElem, class Head, class... Tail>
struct replaceAt<Zero, NewElem, Head, Tail...> {
$return List<NewElem, Tail...>;
};
template<class N, class NewElem, class Head, class... Tail>
struct replaceAt<succ<N>, NewElem, Head, Tail...> {
using rest = typename replaceAt<N, NewElem, Tail...>::result;
template<class RestList>
struct prepend;
template<class... Rs>
struct prepend<List<Rs...>> {
$return List<Head, Rs...>;
};
$return typename prepend<rest>::result;
};
// Forward declarations
template<class S, class... CountedThreads>
struct scheduleLoop;
template<class S, class Index, class... CountedThreads>
struct runThread;
// Main schedule function: wraps threads with counter 0
template<class S, class... Threads>
struct schedule {
$return call<scheduleLoop<S, CountedThread<Zero, Threads>...>>;
};
// scheduleLoop [] = return ()
template<class S>
struct scheduleLoop<S> {
$return Unit;
};
// scheduleLoop with threads
template<class S, class... CountedThreads>
struct scheduleLoop {
using finder = findAwake<Zero, CountedThreads...>;
// If no awake thread found, decrement all and try again
// If awake thread found, run it
template<bool Found, class Dummy = void>
struct dispatch;
// No awake thread: decrement all and loop
template<class Dummy>
struct dispatch<false, Dummy> {
using decremented = call<decrementAll<CountedThreads...>>;
template<class L>
struct applyLoop;
template<class... Ts>
struct applyLoop<List<Ts...>> {
$return call<scheduleLoop<S, Ts...>>;
};
$return call<applyLoop<decremented>>;
};
// Awake thread found: run it
template<class Dummy>
struct dispatch<true, Dummy> {
$return call<runThread<S, typename finder::index, CountedThreads...>>;
};
$return call<dispatch<finder::found::value>>;
};
// runThread: run the thread at given index
template<class S, class Index, class... CountedThreads>
struct runThread {
using current_thread = call<getAt<Index, CountedThreads...>>;
using thread = typename current_thread::thread;
// Dispatch based on thread type
template<class T>
struct dispatch;
// Thread finished (Pure): remove from list and continue loop
template<class A>
struct dispatch<SleepPure<S, A>> {
using remaining = call<removeAt<Index, CountedThreads...>>;
template<class L>
struct applyLoop;
template<class... Ts>
struct applyLoop<List<Ts...>> {
$return call<scheduleLoop<S, Ts...>>;
};
$return call<applyLoop<remaining>>;
};
// State action (FLeft): execute, update thread, decrement others, run again
template<class StateAction, class Next>
struct dispatch<SleepFreeLeft<S, StateAction, Next>> {
// Replace current thread with (0, Next), decrement others
using updated = call<replaceAt<Index, CountedThread<Zero, Next>, CountedThreads...>>;
template<class L>
struct applyDecrementAndRun;
template<class... Ts>
struct applyDecrementAndRun<List<Ts...>> {
using decremented = call<decrementOthers<Index, Zero, Ts...>>;
template<class L2>
struct applyRun;
template<class... Ts2>
struct applyRun<List<Ts2...>> {
$return call<runThread<S, Index, Ts2...>>;
};
$return call<applyRun<decremented>>;
};
$return call<applyDecrementAndRun<updated>>;
};
// Sleep (FRight): set counter and loop
template<class N, class Next>
struct dispatch<SleepFreeRight<S, N, Next>> {
// Replace current thread with (N, Next) and loop
using updated = call<replaceAt<Index, CountedThread<N, Next>, CountedThreads...>>;
template<class L>
struct applyLoop;
template<class... Ts>
struct applyLoop<List<Ts...>> {
$return call<scheduleLoop<S, Ts...>>;
};
$return call<applyLoop<updated>>;
};
$return call<dispatch<thread>>;
};
// Tests for schedule
// Empty schedule returns Unit
static_assert(equals<call<schedule<int>>, Unit>);
// Single Pure thread returns Unit
static_assert(equals<call<schedule<int, SleepPure<int, Unit>>>, Unit>);
// Two Pure threads return Unit
static_assert(equals<call<schedule<int, SleepPure<int, Unit>, SleepPure<int, Unit>>>, Unit>);
// Thread with state action then Pure
using StateActionThread4 = SleepFreeLeft<int, StateT<int, Unit>, SleepPure<int, Unit>>;
static_assert(equals<call<schedule<int, StateActionThread4>>, Unit>);
// Thread with sleep then Pure
using One = succ<Zero>;
using Two = succ<One>;
using Three = succ<Two>;
using Four = succ<Three>;
using SleepThread = SleepFreeRight<int, Two, SleepPure<int, Unit>>;
static_assert(equals<call<schedule<int, SleepThread>>, Unit>);
// Multiple threads with sleep
using Thread1Sleep = SleepFreeRight<int, One, SleepPure<int, Unit>>;
using Thread2Pure = SleepPure<int, Unit>;
static_assert(equals<call<schedule<int, Thread1Sleep, Thread2Pure>>, Unit>);
// ============================================================
// Question 4 Tests: schedule (from README)
//
// appendChar :: Char -> SleepState String ()
// appendChar c = do
// s <- getZ
// putZ (s ++ [c])
//
// sleepThread1 :: SleepState String ()
// sleepThread1 = do
// appendChar 'a' -- 2 steps
// appendChar 'a' -- 2 steps
// sleep 4
// appendChar 'a' -- 2 steps
//
// sleepThread2 :: SleepState String ()
// sleepThread2 = do
// sleep 2
// appendChar 'b' -- 2 steps
// appendChar 'b' -- 2 steps
//
// sleepThread3 :: SleepState String ()
// sleepThread3 = do
// appendChar 'c' -- 2 steps
// sleep 2
// appendChar 'c' -- 2 steps
//
// sleepExample :: [SleepState String ()]
// sleepExample = [sleepThread1, sleepThread2, sleepThread3]
//
// ghci> execState (schedule sleepExample) ""
// "aacbbac"
// ============================================================
// Test: Thread that does 2 state actions then finishes
// appendChar equivalent: getZ >>= putZ >>= Pure
using AppendChar = SleepFreeLeft<int, StateT<int, Unit>,
SleepFreeLeft<int, StateT<int, Unit>,
SleepPure<int, Unit>>>;
static_assert(equals<call<schedule<int, AppendChar>>, Unit>);
// Test: Thread that sleeps immediately, then finishes
// sleep 2 >>= Pure
using SleepThenPure = SleepFreeRight<int, Two, SleepPure<int, Unit>>;
static_assert(equals<call<schedule<int, SleepThenPure>>, Unit>);
// Test: Thread that does action, sleeps, then finishes
// Similar to sleepThread3: appendChar 'c' -> sleep 2 -> appendChar 'c'
using ActionSleepAction = SleepFreeLeft<int, StateT<int, Unit>, // getZ
SleepFreeLeft<int, StateT<int, Unit>, // putZ
SleepFreeRight<int, Two, // sleep 2
SleepFreeLeft<int, StateT<int, Unit>, // getZ
SleepFreeLeft<int, StateT<int, Unit>, // putZ
SleepPure<int, Unit>>>>>>;
static_assert(equals<call<schedule<int, ActionSleepAction>>, Unit>);
// Test: sleepThread1 structure
// appendChar 'a' (2 steps) -> appendChar 'a' (2 steps) -> sleep 4 -> appendChar 'a' (2 steps)
using SleepThread1 = SleepFreeLeft<int, StateT<int, Unit>, // getZ
SleepFreeLeft<int, StateT<int, Unit>, // putZ
SleepFreeLeft<int, StateT<int, Unit>, // getZ
SleepFreeLeft<int, StateT<int, Unit>, // putZ
SleepFreeRight<int, Four, // sleep 4
SleepFreeLeft<int, StateT<int, Unit>, // getZ
SleepFreeLeft<int, StateT<int, Unit>, // putZ
SleepPure<int, Unit>>>>>>>>;
static_assert(equals<call<schedule<int, SleepThread1>>, Unit>);
// Test: sleepThread2 structure
// sleep 2 -> appendChar 'b' (2 steps) -> appendChar 'b' (2 steps)
using SleepThread2 = SleepFreeRight<int, Two,
SleepFreeLeft<int, StateT<int, Unit>,
SleepFreeLeft<int, StateT<int, Unit>,
SleepFreeLeft<int, StateT<int, Unit>,
SleepFreeLeft<int, StateT<int, Unit>,
SleepPure<int, Unit>>>>>>;
static_assert(equals<call<schedule<int, SleepThread2>>, Unit>);
// Test: sleepThread3 structure
// appendChar 'c' (2 steps) -> sleep 2 -> appendChar 'c' (2 steps)
using SleepThread3 = SleepFreeLeft<int, StateT<int, Unit>,
SleepFreeLeft<int, StateT<int, Unit>,
SleepFreeRight<int, Two,
SleepFreeLeft<int, StateT<int, Unit>,
SleepFreeLeft<int, StateT<int, Unit>,
SleepPure<int, Unit>>>>>>;
static_assert(equals<call<schedule<int, SleepThread3>>, Unit>);
// Test: Full sleepExample from README
// sleepExample = [sleepThread1, sleepThread2, sleepThread3]
// Expected: "aacbbac"
static_assert(equals<call<schedule<int, SleepThread1, SleepThread2, SleepThread3>>, Unit>);
// Test: Multiple threads, all sleeping initially
using AllSleeping1 = SleepFreeRight<int, One, SleepPure<int, Unit>>;
using AllSleeping2 = SleepFreeRight<int, Two, SleepPure<int, Unit>>;
static_assert(equals<call<schedule<int, AllSleeping1, AllSleeping2>>, Unit>);
// Test: Priority ordering - first thread has priority
// T1: sleep 1 -> Pure, T2: Pure
// T2 runs first (awake), T1 wakes up after 1 step, finishes
using PriorityT1 = SleepFreeRight<int, One, SleepPure<int, Unit>>;
using PriorityT2 = SleepPure<int, Unit>;
static_assert(equals<call<schedule<int, PriorityT1, PriorityT2>>, Unit>);
// Test: Long sleep chains
using LongSleep = SleepFreeRight<int, Four,
SleepFreeRight<int, Two,
SleepPure<int, Unit>>>;
static_assert(equals<call<schedule<int, LongSleep>>, Unit>);
// Test: Interleaved actions with multiple threads
// T1: action -> action -> Pure
// T2: sleep 1 -> action -> Pure
// T1 runs 2 actions (decrementing T2), T2 wakes and runs, both finish
using InterleavedT1 = SleepFreeLeft<int, StateT<int, Unit>,
SleepFreeLeft<int, StateT<int, Unit>,
SleepPure<int, Unit>>>;
using InterleavedT2 = SleepFreeRight<int, One,
SleepFreeLeft<int, StateT<int, Unit>,
SleepPure<int, Unit>>>;
static_assert(equals<call<schedule<int, InterleavedT1, InterleavedT2>>, Unit>);
// ============================================================
// STATEFUL SCHEDULER WITH ACTUAL STATE TRACKING
// This properly threads state through and produces "aacbbac"
// ============================================================
// ============================================================
// Type-level Char using Nat encoding
// Char<N> where N is a Peano natural representing the ASCII value
// ============================================================
template<class N>
struct Char {
using value = N;
$return N;
};
template<int N>
struct NatFromInt {
$return succ<typename NatFromInt<N-1>::result>;
};
template<>
struct NatFromInt<0> {
$return Zero;
};
// ch<'a'> produces Char<Nat<97>> etc.
template<char C>
struct ch {
$return Char<typename NatFromInt<static_cast<int>(C)>::result>;
};
using CharA = call<ch<'a'>>;
using CharB = call<ch<'b'>>;
using CharC = call<ch<'c'>>;
static_assert(equals<CharA, Char<call<NatFromInt<97>>>>);
static_assert(equals<CharB, Char<call<NatFromInt<98>>>>);
static_assert(equals<CharC, Char<call<NatFromInt<99>>>>);
// Different characters should be different types
static_assert(not equals<CharA, CharB>);
static_assert(not equals<CharB, CharC>);
static_assert(not equals<CharA, CharC>);
// Same character should be same type
static_assert(equals<call<ch<'a'>>, call<ch<'a'>>>);
static_assert(equals<call<ch<'z'>>, call<ch<'z'>>>);
// String as a type-level list of characters
template<class... Chars>
struct String {};
// Thread types for the stateful scheduler
// StatefulPure: thread finished
template<class A>
struct StatefulPure {};
// StatefulAppend<C, Next>: append char C, then continue with Next
template<class C, class Next>
struct StatefulAppend {};
// StatefulSleep<N, Next>: sleep for N steps, then continue with Next
template<class N, class Next>
struct StatefulSleep {};
// Counted thread with state
template<class Counter, class Thread>
struct StatefulCountedThread {
using counter = Counter;
using thread = Thread;
};
// ============================================================
// Helper functions for StatefulCountedThread
// ============================================================
// Find first awake thread (counter == 0)
template<class Index, class... Threads>
struct statefulFindAwake;
template<class Index>
struct statefulFindAwake<Index> {
using found = std::false_type;
using index = void;
};
template<class Index, class Counter, class Thread, class... Rest>
struct statefulFindAwake<Index, StatefulCountedThread<Counter, Thread>, Rest...> {
using found = std::conditional_t<
is_zero<Counter>::value,
std::true_type,
typename statefulFindAwake<succ<Index>, Rest...>::found
>;
using index = std::conditional_t<
is_zero<Counter>::value,
Index,
typename statefulFindAwake<succ<Index>, Rest...>::index
>;
};
// Decrement all counters
template<class... Threads>
struct statefulDecrementAll;
template<>
struct statefulDecrementAll<> {
$return List<>;
};
template<class Counter, class Thread, class... Rest>
struct statefulDecrementAll<StatefulCountedThread<Counter, Thread>, Rest...> {
using rest_decremented = call<statefulDecrementAll<Rest...>>;
template<class RestList>
struct prepend;
template<class... Rs>
struct prepend<List<Rs...>> {
$return List<StatefulCountedThread<call<decrement<Counter>>, Thread>, Rs...>;
};
$return call<prepend<rest_decremented>>;
};
// Decrement all except the active thread at index
template<class ActiveIdx, class CurrIdx, class... Threads>
struct statefulDecrementOthers;
template<class ActiveIdx, class CurrIdx>
struct statefulDecrementOthers<ActiveIdx, CurrIdx> {
$return List<>;
};
template<class ActiveIdx, class CurrIdx, class Counter, class Thread, class... Rest>
struct statefulDecrementOthers<ActiveIdx, CurrIdx, StatefulCountedThread<Counter, Thread>, Rest...> {
using rest_decremented = call<statefulDecrementOthers<ActiveIdx, succ<CurrIdx>, Rest...>>;
static constexpr bool is_active = equals<ActiveIdx, CurrIdx>;
using new_counter = std::conditional_t<is_active, Counter, call<decrement<Counter>>>;
template<class RestList>
struct prepend;
template<class... Rs>
struct prepend<List<Rs...>> {
$return List<StatefulCountedThread<new_counter, Thread>, Rs...>;
};
$return call<prepend<rest_decremented>>;
};
// Get element at index for StatefulCountedThread
template<class Index, class... Threads>
struct statefulGetAt;
template<class Head, class... Tail>
struct statefulGetAt<Zero, Head, Tail...> {
$return Head;
};
template<class N, class Head, class... Tail>
struct statefulGetAt<succ<N>, Head, Tail...> {
$return typename statefulGetAt<N, Tail...>::result;
};
// Remove element at index
template<class Index, class... Threads>
struct statefulRemoveAt;
template<class Head, class... Tail>
struct statefulRemoveAt<Zero, Head, Tail...> {
$return List<Tail...>;
};
template<class N, class Head, class... Tail>
struct statefulRemoveAt<succ<N>, Head, Tail...> {
using rest = typename statefulRemoveAt<N, Tail...>::result;
template<class RestList>
struct prepend;
template<class... Rs>
struct prepend<List<Rs...>> {
$return List<Head, Rs...>;
};
$return typename prepend<rest>::result;
};
// Replace element at index
template<class Index, class NewElem, class... Threads>
struct statefulReplaceAt;
template<class NewElem, class Head, class... Tail>
struct statefulReplaceAt<Zero, NewElem, Head, Tail...> {
$return List<NewElem, Tail...>;
};
template<class N, class NewElem, class Head, class... Tail>
struct statefulReplaceAt<succ<N>, NewElem, Head, Tail...> {
using rest = typename statefulReplaceAt<N, NewElem, Tail...>::result;
template<class RestList>
struct prepend;
template<class... Rs>
struct prepend<List<Rs...>> {
$return List<Head, Rs...>;
};
$return typename prepend<rest>::result;
};
// ============================================================
// execSchedule: Run scheduler with state threading
// Returns the final state (String)
// ============================================================
template<class State, class... CountedThreads>
struct execScheduleLoop;
template<class State, class Index, class... CountedThreads>
struct execRunThread;
// Main entry point: wrap threads with counter 0
template<class InitState, class... Threads>
struct execSchedule {
$return call<execScheduleLoop<InitState, StatefulCountedThread<Zero, Threads>...>>;
};
// Empty list: return current state
template<class State>
struct execScheduleLoop<State> {
$return State;
};
// Find first awake thread and run it, or decrement all if all sleeping
template<class State, class... CountedThreads>
struct execScheduleLoop {
using finder = statefulFindAwake<Zero, CountedThreads...>;
template<bool Found, class Dummy = void>
struct dispatch;
// No awake thread: decrement all and loop
template<class Dummy>
struct dispatch<false, Dummy> {
using decremented = call<statefulDecrementAll<CountedThreads...>>;
template<class L>
struct applyLoop;
template<class... Ts>
struct applyLoop<List<Ts...>> {
$return call<execScheduleLoop<State, Ts...>>;
};
$return call<applyLoop<decremented>>;
};
// Awake thread found: run it
template<class Dummy>
struct dispatch<true, Dummy> {
$return call<execRunThread<State, typename finder::index, CountedThreads...>>;
};
$return call<dispatch<finder::found::value>>;
};
// Run thread at given index
template<class State, class Index, class... CountedThreads>
struct execRunThread {
using current_thread = typename statefulGetAt<Index, CountedThreads...>::result;
using thread = typename current_thread::thread;
template<class T>
struct dispatch;
// Thread finished (Pure): remove from list and continue loop
template<class A>
struct dispatch<StatefulPure<A>> {
using remaining = typename statefulRemoveAt<Index, CountedThreads...>::result;
template<class L>
struct applyLoop;
template<class... Ts>
struct applyLoop<List<Ts...>> {
$return call<execScheduleLoop<State, Ts...>>;
};
$return call<applyLoop<remaining>>;
};
// AppendChar: modify state, update thread, decrement others, run again
template<class C, class Next>
struct dispatch<StatefulAppend<C, Next>> {
// Append C to State
template<class S, class Ch>
struct appendToString;
template<class... Chars, class Ch>
struct appendToString<String<Chars...>, Ch> {
$return String<Chars..., Ch>;
};
using NewState = call<appendToString<State, C>>;
// Replace current thread with (0, Next)
using updated = statefulReplaceAt<Index, StatefulCountedThread<Zero, Next>, CountedThreads...>::result;
// Decrement others
template<class L>
struct applyDecrementAndRun;
template<class... Ts>
struct applyDecrementAndRun<List<Ts...>> {
using decremented = call<statefulDecrementOthers<Index, Zero, Ts...>>;
template<class L2>
struct applyRun;
template<class... Ts2>
struct applyRun<List<Ts2...>> {
$return call<execRunThread<NewState, Index, Ts2...>>;
};
$return call<applyRun<decremented>>;
};
$return call<applyDecrementAndRun<updated>>;
};
// Sleep: set counter and loop
template<class N, class Next>
struct dispatch<StatefulSleep<N, Next>> {
using updated = typename statefulReplaceAt<Index, StatefulCountedThread<N, Next>, CountedThreads...>::result;
template<class L>
struct applyLoop;
template<class... Ts>
struct applyLoop<List<Ts...>> {
$return call<execScheduleLoop<State, Ts...>>;
};
$return call<applyLoop<updated>>;
};
$return call<dispatch<thread>>;
};
// ============================================================
// Define the three threads from README
// appendChar is 2 steps: getZ (1 step) + putZ (1 step)
// ============================================================
// sleepThread1 :: SleepState String ()
// sleepThread1 = do
// appendChar 'a' -- 2 steps: getZ, putZ
// appendChar 'a' -- 2 steps: getZ, putZ
// sleep 4
// appendChar 'a' -- 2 steps: getZ, putZ
//
// Total: 6 state actions split by sleep 4
using StatefulThread1 = StatefulAppend<CharA, // step 1 (appendChar 'a' part 1)
StatefulAppend<CharA, // step 2 (appendChar 'a' part 2 - simplified to single append)
StatefulSleep<Four, // sleep 4
StatefulAppend<CharA, // step 5-6 (appendChar 'a')
StatefulPure<Unit>>>>>;
// sleepThread2 :: SleepState String ()
// sleepThread2 = do
// sleep 2
// appendChar 'b' -- 2 steps
// appendChar 'b' -- 2 steps
using StatefulThread2 = StatefulSleep<Two, // sleep 2
StatefulAppend<CharB, // appendChar 'b'
StatefulAppend<CharB, // appendChar 'b'
StatefulPure<Unit>>>>;
// sleepThread3 :: SleepState String ()
// sleepThread3 = do
// appendChar 'c' -- 2 steps
// sleep 2
// appendChar 'c' -- 2 steps
using StatefulThread3 = StatefulAppend<CharC, // appendChar 'c'
StatefulSleep<Two, // sleep 2
StatefulAppend<CharC, // appendChar 'c'
StatefulPure<Unit>>>>;
// ============================================================
// TEST: Verify the scheduler produces "aacbbac"
//
// Execution trace (following README rules):
// - All threads start with counter 0
// - T1 is awake, runs appendChar 'a' (1 step, decrements T2/T3 counters - but they're 0)
// - T1 continues (still awake), runs appendChar 'a' (1 step)
// - T1 sleeps 4 (counter = 4), find next awake
// - T3 is awake (counter 0), runs appendChar 'c' (1 step, T1 counter 4->3, T2 counter 0)
// - T3 sleeps 2 (counter = 2), find next awake
// - T2 is awake (counter 0 after T1's 2 steps decremented it from 2->0), runs appendChar 'b'
// - T2 continues, runs appendChar 'b' (T1 counter 3->1, T3 counter 2->0)
// - T2 finishes, removed
// - T1 counter 1, T3 counter 0. T3 awake, runs appendChar 'c'
// - T3 finishes (T1 counter 1->0)
// - T1 awake, runs appendChar 'a'
// - T1 finishes
// Result: a, a, c, b, b, a, c = "aacbbac"
// ============================================================
// Expected result: String<CharA, CharA, CharC, CharB, CharB, CharA, CharC>
// which represents "aacbbac"
using ExpectedResult = String<CharA, CharA, CharC, CharB, CharB, CharA, CharC>;
// Run the scheduler with empty initial state
using ActualResult = call<execSchedule<String<>, StatefulThread1, StatefulThread2, StatefulThread3>>;
// THE KEY TEST: Verify we get "aacbbac"
static_assert(equals<ActualResult, ExpectedResult>, "Schedule should produce 'aacbbac'");
// ============================================================
// EXTENSIVE TESTS FOR QUESTION 1: toRose and fromRose
// ============================================================
// Test 1.1: Single leaf
using Q1_Leaf1 = Leaf<Zero>;
static_assert(equals<call<toRose<call<fromRose<Q1_Leaf1>>>>, Q1_Leaf1>);
// Test 1.2: Single Pure
using Q1_Pure1 = Pure<List, succ<succ<succ<Zero>>>>;
static_assert(equals<call<fromRose<call<toRose<Q1_Pure1>>>>, Q1_Pure1>);
// Test 1.3: Empty branch
using Q1_EmptyBranch = Branch<>;
using Q1_EmptyBranchFree = call<fromRose<Q1_EmptyBranch>>;
using Q1_EmptyBranchRoundTrip = call<toRose<Q1_EmptyBranchFree>>;
static_assert(equals<Q1_EmptyBranchRoundTrip, Q1_EmptyBranch>);
// Test 1.4: Branch with one leaf
using Q1_SingleChildBranch = Branch<Leaf<succ<Zero>>>;
static_assert(equals<call<toRose<call<fromRose<Q1_SingleChildBranch>>>>, Q1_SingleChildBranch>);
// Test 1.5: Branch with multiple leaves
using Q1_MultiLeafBranch = Branch<Leaf<Zero>, Leaf<succ<Zero>>, Leaf<succ<succ<Zero>>>, Leaf<succ<succ<succ<Zero>>>>>;
static_assert(equals<call<toRose<call<fromRose<Q1_MultiLeafBranch>>>>, Q1_MultiLeafBranch>);
// Test 1.6: Deeply nested tree
using Q1_Deep = Branch<Branch<Branch<Leaf<Zero>>>>;
static_assert(equals<call<toRose<call<fromRose<Q1_Deep>>>>, Q1_Deep>);
// Test 1.7: Complex tree structure
using Q1_Complex = Branch<
Branch<Leaf<Zero>, Leaf<succ<Zero>>>,
Leaf<succ<succ<Zero>>>,
Branch<Leaf<succ<succ<succ<Zero>>>>, Branch<Leaf<succ<succ<succ<succ<Zero>>>>>>>
>;
static_assert(equals<call<toRose<call<fromRose<Q1_Complex>>>>, Q1_Complex>);
// Test 1.8: Free with nested structure round-trip
using Q1_FreeDirect = Free<List, List<Pure<List, Zero>, Free<List, List<Pure<List, succ<Zero>>>>>>;
static_assert(equals<call<fromRose<call<toRose<Q1_FreeDirect>>>>, Q1_FreeDirect>);
// ============================================================
// EXTENSIVE TESTS FOR QUESTION 2: trace
// ============================================================
// Test 2.1: trace of Pure returns the value unchanged
using Q2_TracePure = call<trace<int, Pure<StateF<int>::template apply, succ<succ<Zero>>>>>;
static_assert(equals<Q2_TracePure, succ<succ<Zero>>>);
// Test 2.2: trace of single Free step
using Q2_SingleStep = Free<StateF<int>::template apply, StateT<int, Pure<StateF<int>::template apply, Unit>>>;
using Q2_TraceSingle = call<trace<int, Q2_SingleStep>>;
static_assert(equals<Q2_TraceSingle, Unit>);
// Test 2.3: trace of multiple Free steps
using Q2_ThreeSteps = Free<StateF<int>::template apply,
StateT<int, Free<StateF<int>::template apply,
StateT<int, Free<StateF<int>::template apply,
StateT<int, Pure<StateF<int>::template apply, Unit>>>>>>>;
using Q2_TraceThree = call<trace<int, Q2_ThreeSteps>>;
static_assert(equals<Q2_TraceThree, Unit>);
// Test 2.4: trace with Pair state type (like Fibonacci)
using Q2_PairState = Pair<int, int>;
using Q2_FibStep = Free<StateF<Q2_PairState>::template apply,
StateT<Q2_PairState, Pure<StateF<Q2_PairState>::template apply, Unit>>>;
using Q2_TraceFib = call<trace<Q2_PairState, Q2_FibStep>>;
static_assert(equals<Q2_TraceFib, Unit>);
// Test 2.5: trace of fibF(0) should return Unit
static_assert(equals<call<trace<Pair<int,int>, call<fibF<Zero>>>>, Unit>);
// Test 2.6: trace of fibF(1) should return Unit
static_assert(equals<call<trace<Pair<int,int>, call<fibF<succ<Zero>>>>>, Unit>);
// Test 2.7: trace of fibF(2) should return Unit
static_assert(equals<call<trace<Pair<int,int>, call<fibF<succ<succ<Zero>>>>>>, Unit>);
// Test 2.8: trace of fibF(3) should return Unit
static_assert(equals<call<trace<Pair<int,int>, call<fibF<succ<succ<succ<Zero>>>>>>>, Unit>);
// ============================================================
// EXTENSIVE TESTS FOR QUESTION 3: roundRobin
// ============================================================
// Test 3.1: Empty list
static_assert(equals<call<roundRobin<int>>, Unit>);
// Test 3.2: Single Pure thread
static_assert(equals<call<roundRobin<int, YieldPure<int, Unit>>>, Unit>);
// Test 3.3: Multiple Pure threads
static_assert(equals<call<roundRobin<int, YieldPure<int, Unit>, YieldPure<int, Unit>, YieldPure<int, Unit>>>, Unit>);
// Test 3.4: Single thread that yields once then finishes
using Q3_YieldOnce = YieldFreeRight<int, YieldPure<int, Unit>>;
static_assert(equals<call<roundRobin<int, Q3_YieldOnce>>, Unit>);
// Test 3.5: Single thread that yields twice then finishes
using Q3_YieldTwice = YieldFreeRight<int, YieldFreeRight<int, YieldPure<int, Unit>>>;
static_assert(equals<call<roundRobin<int, Q3_YieldTwice>>, Unit>);
// Test 3.6: Two threads, first yields, second is Pure
// First yields -> moves to back, Second runs (finishes), First runs (finishes)
using Q3_TwoThreads1 = YieldFreeRight<int, YieldPure<int, Unit>>;
using Q3_TwoThreads2 = YieldPure<int, Unit>;
static_assert(equals<call<roundRobin<int, Q3_TwoThreads1, Q3_TwoThreads2>>, Unit>);
// Test 3.7: Both threads yield once
using Q3_BothYield1 = YieldFreeRight<int, YieldPure<int, Unit>>;
using Q3_BothYield2 = YieldFreeRight<int, YieldPure<int, Unit>>;
static_assert(equals<call<roundRobin<int, Q3_BothYield1, Q3_BothYield2>>, Unit>);
// Test 3.8: Thread with state action before yield
using Q3_StateYield = YieldFreeLeft<int, StateT<int, Unit>, YieldFreeRight<int, YieldPure<int, Unit>>>;
static_assert(equals<call<roundRobin<int, Q3_StateYield>>, Unit>);
// Test 3.9: Three threads interleaving with multiple yields
using Q3_T1 = YieldFreeRight<int, YieldFreeRight<int, YieldPure<int, Unit>>>;
using Q3_T2 = YieldFreeRight<int, YieldPure<int, Unit>>;
using Q3_T3 = YieldPure<int, Unit>;
static_assert(equals<call<roundRobin<int, Q3_T1, Q3_T2, Q3_T3>>, Unit>);
// Test 3.10: Complex pattern - state, yield, state, yield, done
using Q3_Complex = YieldFreeLeft<int, StateT<int, Unit>,
YieldFreeRight<int,
YieldFreeLeft<int, StateT<int, Unit>,
YieldFreeRight<int,
YieldPure<int, Unit>>>>>;
static_assert(equals<call<roundRobin<int, Q3_Complex>>, Unit>);
// ============================================================
// STATEFUL ROUND-ROBIN WITH ACTUAL STATE TRACKING
// From README:
// charWriter :: Char -> YieldState String ()
// charWriter c = do s <- getY
// if (length s > 10) then pure () else
// do putY (c:s)
// yield
// charWriter c
//
// yieldExample = [charWriter 'a', charWriter 'b', charWriter 'c']
// ghci> execState (roundRobin yieldExample) ""
// "bacbacbacba"
// ============================================================
// Thread types for stateful round-robin
template<class A>
struct RRPure {};
// RRAppend<C, Next>: prepend char C to state, then continue with Next
template<class C, class Next>
struct RRPrepend {};
// RRYield<Next>: yield control, then continue with Next
template<class Next>
struct RRYield {};
// RRCheck<C>: check if length > 10, if so Pure, else prepend and yield and recurse
// We'll model the charWriter directly
template<class C, class LenLimit>
struct RRCharWriter {};
// ============================================================
// execRoundRobin: Run round-robin scheduler with state threading
// ============================================================
template<class State, class... Threads>
struct execRoundRobinLoop;
// Main entry point
template<class InitState, class... Threads>
struct execRoundRobin {
$return call<execRoundRobinLoop<InitState, Threads...>>;
};
// Empty list: return current state
template<class State>
struct execRoundRobinLoop<State> {
$return State;
};
// Pure thread: remove from list and continue
template<class State, class A, class... Rest>
struct execRoundRobinLoop<State, RRPure<A>, Rest...> {
$return call<execRoundRobinLoop<State, Rest...>>;
};
// Prepend thread: modify state and continue with next (stays at front)
template<class State, class C, class Next, class... Rest>
struct execRoundRobinLoop<State, RRPrepend<C, Next>, Rest...> {
// Prepend C to State
template<class S, class Ch>
struct prependToString;
template<class... Chars, class Ch>
struct prependToString<String<Chars...>, Ch> {
$return String<Ch, Chars...>;
};
using NewState = call<prependToString<State, C>>;
$return call<execRoundRobinLoop<NewState, Next, Rest...>>;
};
// Yield thread: move to back and continue with rest
template<class State, class Next, class... Rest>
struct execRoundRobinLoop<State, RRYield<Next>, Rest...> {
$return call<execRoundRobinLoop<State, Rest..., Next>>;
};
// String length helper
template<class S>
struct stringLength;
template<class... Chars>
struct stringLength<String<Chars...>> {
static constexpr int value = sizeof...(Chars);
};
// CharWriter: check length, if > 10 then Pure, else prepend, yield, recurse
template<class State, class C, class Limit, class... Rest>
struct execRoundRobinLoop<State, RRCharWriter<C, Limit>, Rest...> {
static constexpr bool done = stringLength<State>::value > 10;
template<bool Done, class Dummy = void>
struct dispatch;
// Length > 10: thread finishes
template<class Dummy>
struct dispatch<true, Dummy> {
$return call<execRoundRobinLoop<State, Rest...>>;
};
// Length <= 10: prepend char, yield, recurse
template<class Dummy>
struct dispatch<false, Dummy> {
template<class S, class Ch>
struct prependToString;
template<class... Chars, class Ch>
struct prependToString<String<Chars...>, Ch> {
$return String<Ch, Chars...>;
};
using NewState = call<prependToString<State, C>>;
// After prepend, yield (move to back)
$return call<execRoundRobinLoop<NewState, Rest..., RRCharWriter<C, Limit>>>;
};
$return call<dispatch<done>>;
};
// ============================================================
// TEST: Verify roundRobin produces "bacbacbacba"
// ============================================================
// charWriter 'a', charWriter 'b', charWriter 'c'
using RRCharWriterA = RRCharWriter<CharA, succ<succ<succ<succ<succ<succ<succ<succ<succ<succ<Zero>>>>>>>>>>>;
using RRCharWriterB = RRCharWriter<CharB, succ<succ<succ<succ<succ<succ<succ<succ<succ<succ<Zero>>>>>>>>>>>;
using RRCharWriterC = RRCharWriter<CharC, succ<succ<succ<succ<succ<succ<succ<succ<succ<succ<Zero>>>>>>>>>>>;
// Run: [charWriter 'a', charWriter 'b', charWriter 'c'] with initial state ""
using RRActualResult = call<execRoundRobin<String<>, RRCharWriterA, RRCharWriterB, RRCharWriterC>>;
// Expected: "bacbacbacba" = prepending gives reversed order in the string type
// When we prepend: a, then b, then c, we get [c,b,a] after first round
// "bacbacbacba" reversed as prepends would be: a,b,c,a,b,c,a,b,c,a,b
// But charWriter prepends, so final string chars in order: b,a,c,b,a,c,b,a,c,b,a
// Wait - let me trace this properly:
// Start: ""
// charWriter 'a': length 0 <= 10, prepend 'a' -> "a", yield, move to back
// charWriter 'b': length 1 <= 10, prepend 'b' -> "ba", yield, move to back
// charWriter 'c': length 2 <= 10, prepend 'c' -> "cba", yield, move to back
// charWriter 'a': length 3 <= 10, prepend 'a' -> "acba", yield
// charWriter 'b': length 4 <= 10, prepend 'b' -> "bacba", yield
// charWriter 'c': length 5 <= 10, prepend 'c' -> "cbacba", yield
// charWriter 'a': length 6 <= 10, prepend 'a' -> "acbacba", yield
// charWriter 'b': length 7 <= 10, prepend 'b' -> "bacbacba", yield
// charWriter 'c': length 8 <= 10, prepend 'c' -> "cbacbacba", yield
// charWriter 'a': length 9 <= 10, prepend 'a' -> "acbacbacba", yield
// charWriter 'b': length 10 <= 10, prepend 'b' -> "bacbacbacba", yield
// charWriter 'c': length 11 > 10, finish
// charWriter 'a': length 11 > 10, finish
// charWriter 'b': length 11 > 10, finish
// Result: "bacbacbacba"
// String representation (most recent prepend first):
using RRExpectedResult = String<CharB, CharA, CharC, CharB, CharA, CharC, CharB, CharA, CharC, CharB, CharA>;
// THE KEY TEST: Verify roundRobin produces "bacbacbacba"
static_assert(equals<RRActualResult, RRExpectedResult>, "RoundRobin should produce 'bacbacbacba'");
// Additional roundRobin state tests
// Test: Empty threads
static_assert(equals<call<execRoundRobin<String<>>>, String<>>);
// Test: Single charWriter that stops immediately (length already > 10)
using RRLongString = String<CharA, CharA, CharA, CharA, CharA, CharA, CharA, CharA, CharA, CharA, CharA>;
static_assert(equals<call<execRoundRobin<RRLongString, RRCharWriterA>>, RRLongString>);
// Test: Simple prepend and pure
using RRSimple = RRPrepend<CharA, RRPure<Unit>>;
static_assert(equals<call<execRoundRobin<String<>, RRSimple>>, String<CharA>>);
// Test: Prepend, yield, pure - only one char since yield moves and then finishes
using RRPrependYield = RRPrepend<CharA, RRYield<RRPure<Unit>>>;
static_assert(equals<call<execRoundRobin<String<>, RRPrependYield>>, String<CharA>>);
// Test: Two threads prepending
using RRT1 = RRPrepend<CharA, RRPure<Unit>>;
using RRT2 = RRPrepend<CharB, RRPure<Unit>>;
// T1 prepends A, T2 prepends B
static_assert(equals<call<execRoundRobin<String<>, RRT1, RRT2>>, String<CharB, CharA>>);
// Test: Two threads with yields
using RRY1 = RRPrepend<CharA, RRYield<RRPure<Unit>>>;
using RRY2 = RRPrepend<CharB, RRYield<RRPure<Unit>>>;
// T1 prepends A, yields (moves back), T2 prepends B, yields (moves back), T1 finishes, T2 finishes
static_assert(equals<call<execRoundRobin<String<>, RRY1, RRY2>>, String<CharB, CharA>>);
// ============================================================
// EXTENSIVE TESTS FOR QUESTION 4: schedule with state tracking
// ============================================================
// Test 4.1: Empty schedule
static_assert(equals<call<execSchedule<String<>>>, String<>>);
// Test 4.2: Single thread with single append
using Q4_SingleAppend = StatefulAppend<CharA, StatefulPure<Unit>>;
static_assert(equals<call<execSchedule<String<>, Q4_SingleAppend>>, String<CharA>>);
// Test 4.3: Single thread with multiple appends
using Q4_ThreeAppends = StatefulAppend<CharA, StatefulAppend<CharB, StatefulAppend<CharC, StatefulPure<Unit>>>>;
static_assert(equals<call<execSchedule<String<>, Q4_ThreeAppends>>, String<CharA, CharB, CharC>>);
// Test 4.4: Thread that only sleeps
using Q4_OnlySleep = StatefulSleep<Three, StatefulPure<Unit>>;
static_assert(equals<call<execSchedule<String<>, Q4_OnlySleep>>, String<>>);
// Test 4.5: Sleep then append
using Q4_SleepAppend = StatefulSleep<Two, StatefulAppend<CharA, StatefulPure<Unit>>>;
static_assert(equals<call<execSchedule<String<>, Q4_SleepAppend>>, String<CharA>>);
// Test 4.6: Append then sleep then append
using Q4_AppendSleepAppend = StatefulAppend<CharA, StatefulSleep<One, StatefulAppend<CharB, StatefulPure<Unit>>>>;
static_assert(equals<call<execSchedule<String<>, Q4_AppendSleepAppend>>, String<CharA, CharB>>);
// Test 4.7: Two threads, both pure
using Q4_TwoPure = StatefulPure<Unit>;
static_assert(equals<call<execSchedule<String<>, Q4_TwoPure, Q4_TwoPure>>, String<>>);
using Q4_FirstAppends = StatefulAppend<CharA, StatefulPure<Unit>>;
using Q4_SecondPure = StatefulPure<Unit>;
static_assert(equals<call<execSchedule<String<>, Q4_FirstAppends, Q4_SecondPure>>, String<CharA>>);
using Q4_PriorityT1 = StatefulAppend<CharA, StatefulPure<Unit>>;
using Q4_PriorityT2 = StatefulAppend<CharB, StatefulPure<Unit>>;
static_assert(equals<call<execSchedule<String<>, Q4_PriorityT1, Q4_PriorityT2>>, String<CharA, CharB>>);
using Q4_SleepWakeT1 = StatefulSleep<One, StatefulAppend<CharA, StatefulPure<Unit>>>;
using Q4_SleepWakeT2 = StatefulAppend<CharB, StatefulPure<Unit>>;
static_assert(equals<call<execSchedule<String<>, Q4_SleepWakeT1, Q4_SleepWakeT2>>, String<CharB, CharA>>);
using Q4_AllSleep1 = StatefulSleep<One, StatefulAppend<CharA, StatefulPure<Unit>>>;
using Q4_AllSleep2 = StatefulSleep<Two, StatefulAppend<CharB, StatefulPure<Unit>>>;
static_assert(equals<call<execSchedule<String<>, Q4_AllSleep1, Q4_AllSleep2>>, String<CharA, CharB>>);
// Test 4.12: Three threads with different sleep times
using Q4_Three1 = StatefulSleep<Three, StatefulAppend<CharA, StatefulPure<Unit>>>;
using Q4_Three2 = StatefulSleep<One, StatefulAppend<CharB, StatefulPure<Unit>>>;
using Q4_Three3 = StatefulAppend<CharC, StatefulPure<Unit>>;
static_assert(equals<call<execSchedule<String<>, Q4_Three1, Q4_Three2, Q4_Three3>>, String<CharC, CharB, CharA>>);
// Test 4.13: Thread that sleeps multiple times
using Q4_MultipleSleep = StatefulAppend<CharA,
StatefulSleep<One,
StatefulAppend<CharB,
StatefulSleep<One,
StatefulAppend<CharC,
StatefulPure<Unit>>>>>>;
static_assert(equals<call<execSchedule<String<>, Q4_MultipleSleep>>, String<CharA, CharB, CharC>>);
using Q4_Inter1 = StatefulAppend<CharA, StatefulSleep<Two, StatefulAppend<CharA, StatefulPure<Unit>>>>;
using Q4_Inter2 = StatefulAppend<CharB, StatefulPure<Unit>>;
static_assert(equals<call<execSchedule<String<>, Q4_Inter1, Q4_Inter2>>, String<CharA, CharB, CharA>>);
static_assert(equals<ActualResult, String<CharA, CharA, CharC, CharB, CharB, CharA, CharC>>);
using Five = succ<Four>;
using Q4_LongSleep = StatefulSleep<Five, StatefulAppend<CharA, StatefulPure<Unit>>>;
static_assert(equals<call<execSchedule<String<>, Q4_LongSleep>>, String<CharA>>);
using Q4_ManyPure = StatefulPure<Unit>;
static_assert(equals<call<execSchedule<String<>, Q4_ManyPure, Q4_ManyPure, Q4_ManyPure, Q4_ManyPure>>, String<>>);
using Q4_LongChain = StatefulAppend<CharA,
StatefulAppend<CharB,
StatefulAppend<CharC,
StatefulAppend<CharA,
StatefulAppend<CharB,
StatefulPure<Unit>>>>>>;
static_assert(equals<call<execSchedule<String<>, Q4_LongChain>>, String<CharA, CharB, CharC, CharA, CharB>>);
int main() { };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment