Question

I'm modelling a round robin scheduler in Haskell.

class Schedulable s where
  isFinal :: s -> Bool

class Scheduler s where
  add :: (Schedulable a) => a -> s -> s
  next :: (Schedulable a) => s -> (a, s)
  empty :: s -> Bool

data Schedulable a => RoundRobin = RoundRobin [a] [a]

instance Scheduler RoundRobin where
  add p (RoundRobin ps qs) = RoundRobin (ps ++ [p]) qs

  next (RoundRobin []     qs) = next (RoundRobin qs [])
  next (RoundRobin (p:ps) qs) = (p, RoundRobin ps (qs ++ [p]))

  empty (RoundRobin [] _) = True
  empty _                 = False

However, GHC complains that

main.hs:9:6:
    Illegal datatype context (use -XDatatypeContexts): Schedulable a =>

How can I solve this issue?

I also saw a proposal about removing datatype contexts, so how can I model the scheduler without using datatype contexts?

Was it helpful?

Solution

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.

OTHER TIPS

Is not recommended to define constraints into data definitions (see Learn Haskell...).

Is preferable set contraints into functions.

(You can, anyway, set DatatypeContexts modifier)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top