-- First some interface code (not part of the exam solution) import qualified Data.Time.Calendar as DTC import qualified Data.Time.Calendar.OrdinalDate as DTCO import qualified Data.Time.Calendar.WeekDate as DTCW import qualified Data.Time.Format as DTF import qualified System.Locale as SL type Date = DTC.Day weekday :: Date -> Weekday weekday d = wd where (_y,_w,wd) = DTCW.toWeekDate d monthday :: Date -> Monthday monthday d = md where (_y,_m,md) = DTC.toGregorian d yearday :: Date -> Yearday yearday = snd . DTCO.toOrdinalDate readD :: String -> Date readD = DTF.readTime SL.defaultTimeLocale "%Y-%m-%d" showD :: Date -> String showD = DTC.showGregorian nextD :: Date -> Date nextD = succ epochD = readD "1970-01-01" ---------------- -- Start of the solution to 2b type Weekday = Int type Monthday = Int type Yearday = Int data DateSet = Once Date | Daily | Weekly Weekday | Monthly Monthday | Yearly Yearday -- | Filter (Date -> Bool) DateSet -- not part of exam question | Union DateSet DateSet | Intersection DateSet DateSet | Start Date | End Date deriving (Show) -- does not work with Filter (emb. function Date->Bool) between :: Date -> Date -> DateSet between start end = Intersection (Start start) (End end) -- The constructor |Once| is not necessary once :: Date -> DateSet once d = between d d isIn :: Date -> DateSet -> Bool isIn d (Once d') = d == d' isIn d Daily = True isIn d (Weekly a) = weekday d == a isIn d (Monthly a) = monthday d == a isIn d (Yearly a) = yearday d == a -- isIn d (Filter p ds) = p d && isIn d ds isIn d (Union x y) = isIn d x || isIn d y isIn d (Intersection x y) = isIn d x && isIn d y isIn d (Start s) = s <= d isIn d (End e) = d <= e -- try to locate an upperBound upperBound :: DateSet -> Date upperBound (Intersection x y) = min (upperBound x) (upperBound y) upperBound (Union x y) = max (upperBound x) (upperBound y) upperBound (End e) = e -- upperBound (Filter p ds) = upperBound ds upperBound (Once d) = d upperBound _ = maxDate maxDate :: Date maxDate = readD "9999-12-31" -- somewhat arbitrary choice -- and a lowerBound (not part of exam question) lowerBound :: DateSet -> Date lowerBound (Intersection x y) = max (lowerBound x) (lowerBound y) lowerBound (Union x y) = min (lowerBound x) (lowerBound y) lowerBound (Start s) = s -- lowerBound (Filter p ds) = lowerBound ds lowerBound (Once d) = d lowerBound _ = minDate minDate :: Date minDate = readD "0000-01-01" -- somewhat arbitrary choice toList :: DateSet -> [Date] toList ds = filter (`isIn` ds) [lowerBound ds .. upperBound ds] -- It is OK to use just |epochD| instead of |lowerBound ds| ---------------- -- Example code test :: DateSet test = Intersection (between (readD "2011-08-23") (readD "2011-12-20")) (Weekly 1) main = print (toList test) ---------------- -- Part b {- Question text: Explain briefly the following EDSL terminology in general: deep embedding, shallow embedding, constructors, combinators and run function. Exemplify by referring to or contrasting with your implementation. Possible answers: * Deep embedding: A DSL implemented near the syntax, deep down from the sematics of the domain, is called a "deep embedding". The code above is an example - the |DateSet| datatype directly captures the abstract syntax of the domain, not the semantics. * Shallow embedding: A DSL implemented near the semantics is called a "shallow embedding". Often it can be constructed by tupling up suitable run-functions. The example above is not done that way (mainly because it is a bit tricky to handle the combination of infinite and finite lists effectively). * constructors Functions which build elements of the domain from "other data". Examples include |Once|, |Daily|, |Weekly|, |Monthly|, |Yearly|, |Start|, |End|. * combinators Functions which build elements of the domain from other elements of the domain (+perhaps some other data). Examples include |Union|, |Intersection|. * run functions Functions from the domain to some semantics. In our example we have |isIn|, |toList| and |upperBound|. -}