I have to pick courses for my first semester of grad school. While it is easy enough to pick 2-3 courses to take, that’s not actually the problem at hand. There are curriculum requirements for both the Master’s and PhD. These are not just courses for one semester, but the beginning of the plan to graduate.
Two questions follow:
- How do we represent the plan?
- How do we build it?
This post will focus on the first question.
In a phrase, denotational design is about defining the meanings in your problem domain before defining the parts of your solution for the problem domain. Define a denotation rather than the implementation. If you don’t understand the problem, then how can you build the solution?
Entities in the Problem Domain
We have courses… of course.
And we have Requirements
From these two types, the problem is starting to take shape. One has to select Courses such that any and all Requirements are satisfied.
One might be tempted to define
type Plan = List Course
and update the problem to be: One has to create a plan that satisfies all Requirements. However, if we think back to the goal of Denotational Design which is to define the concepts in our problem domain, then we realize we should punt on defining
Plan because it is part of a potential implementation.
We could flesh out what exactly a course is, but this leads up down the road of implementation. The fact that a course is is enough for the purpose of our Denotational Design process.
When I started writing this, I had the idea that a requirement is:
type Requirement = [Course]
However this does not represent that notion that a requirementis something to be satisfied. This suggests:
type Requirement = [Course] -> Boolean
HOWEVER, both of these fail when we compare these definitions with our actual problem.
Top Level Requirements: Phd | Master's Level Two Reqs: Breadth | Depth | Electives Level Three: HCI Focus | Systems Focus | etc.
Requirements need to be able to include other requirements. Distraught, I reached out to Sandy and told him of the tar pit that denotational design had dumped me in. He sat back, put on his hat, did a sick kick flip, and told me this
I don’t think you’d want to encode those in your domain.
you just want an “is valid”
And thusly I was enlightened. The problem domain does NOT have any such concept as a
Requirement. It is more helpful to consider
type Validator = [Course] -> Boolean
At the end of the day the problem is this: Is the list of courses that I’ve chosen acceptable, yes or no?
And in the spirit of being Resonably Polymorphic™© we could even simplify further!
type Validator a = [a] -> Boolean
Now we don’t even have to ponder that nasty, “what is a course, really?” question. Or rather, BECAUSE we realized that it doesn’t matter what the course is, just that it is a type of thing that exists, we can drop the implementation detail that is a course and work entirely with validators.
Can we envision a solution?
Let’s see if we can explain the actions we’d like to take entirely in terms of the Validator type. If we can define the following actions, then I posit that Validator is sufficient:
- Checking if one can take a course
- Checking if a selection of courses is valid for graduation
A course can be thought of as a validator in the making:
canITakeCourse :: Course -> [Course] -> Bool canITakeCourse thisCourse otherSelectedCourses = ... -- Which can be used to create functions like canITakeCS101 :: [Course] -> Bool -- Which can be written as canITakeCS101 :: Validator Course
Course is a catch-all for any information that becomes relevant such as name, code, and pre-requisites. Through partial function application we are able to produce a Validator representing the ability to take any course.
Valid for Graduation
function canIGraduateWithThis :: [Course] -> [Validator [Course]] -> Bool -- [Course] -> [[Course] -> Bool] -> Bool
In English, we have a function that takes our chosen list of courses, a set of Validators that act on a list of courses, and finally we get the result of whether or not our selection is valid.
As someone who does front-end development, I enjoy having rich representations of the data that I’m presenting to the user. This enables me to empower the user to do whatever they need to do with their data. However, these are incredibly implementation specific. Going through the denotative design process has provided me some insight on how to distill the essence of the problem that I’m actually solving from the concerns of displaying it to the end user. Not sure that I’ve done it 100% according to the vision of Conal Elliot, but I look forward to going through the process again.