Your Scheduler
class
class Scheduler s where
add :: (Schedulable a) => a -> s -> s
next :: (Schedulable a) => s -> (a, s)
empty :: s -> Bool
is not going to work.
add
promises that values of any Schedulable
type can be added to the scheduler. That is possible, but it requires an extension, ExistentialQuantification
or GADTs
would allow to define a type wrapping any Schedulable
value.
next
, however, promises to deliver a value of any Schedulable
type, whatever the caller desires, and that's not going to work. If I only ever add Int
values to the scheduler, and then ask for a String
, how would it construct one out of thin air?
One way to get your code to work is
{-# LANGUAGE GADTs #-}
module Schedules where
class Schedulable s where
isFinal :: s -> Bool
class Scheduler s where
add :: (Schedulable a) => a -> s -> s
next :: s -> (Schedule, s) -- returns a Schedulable item of unknown type, wrapped in a Schedule
empty :: s -> Bool
-- Wrapper type for any Schedulable
data Schedule where
Schedule :: Schedulable a => a -> Schedule
-- Equivalent alternative using existential quantification instead of GADT syntax
-- data Schedule = forall a. Schedulable a => Schedule a
-- make Schedules Schedulable, maybe not necessary
instance Schedulable Schedule where
isFinal (Schedule s) = isFinal s
-- RoundRobin queues schedulable items, wrapped as Schedules, since lists are homogeneous
data RoundRobin = RoundRobin [Schedule] [Schedule]
-- How RoundRobin works
instance Scheduler RoundRobin where
-- enqueue item after wrapping it
add p (RoundRobin ps qs) = RoundRobin (ps ++ [Schedule p]) qs
-- deliver next item to process
-- the first equation suggests that (Maybe Schedule, s) would be the better return type
next (RoundRobin [] []) = error "Nothing to schedule"
next (RoundRobin [] qs) = next (RoundRobin qs [])
next (RoundRobin (p:ps) qs) = (p, RoundRobin ps (qs ++ [p]))
empty (RoundRobin [] _) = True
empty _ = False
Using GADT
syntax or existential quantification makes the constraints imposed on the constructor available via pattern matching, in contrast to the old DatatypeContexts
that despite the constraints on the type, required the context put on the functions using the type.