Created
February 12, 2026 17:45
-
-
Save Frityet/5691ae628513fb0e84f5672a464f1bc4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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