|Part I||January 28th (Thursday, course week 2)|
|Part II||February 4th (Thursday, course week 3)|
This lab assignment asks you to implement a library for so-called turtle graphics. The library will be interfaced to as an embedded language. Several extensions are then made to the original turtle language.
We recommend that you implement the graphics part of the lab using HGL, a simple graphics library with just enough features for this assignment. If you prefer (or if you plan on using 3D) you may use one of the OpenGL libraries or you may use gtk2hs. If you would like to use a different graphics library please ask before starting the assignment.
The idea of turtle graphics was originally part of the Logo programming language. It originated from an environment where a real robot (the "turtle") could move around and act on simple commands. It was successfully used at MIT to teach children to learn programming (check this blog post to see why). The two basic commands it understood were:
forward n right d
n is the number of steps the robot should move forward, and
number of degrees the robot should turn. More information can be found on the
Turtle graphics wikipedia page
(or in a local copy of the Logo
idea is that you will implement a part of this turtle graphics language. Your
program will be able to produce something like the following output:
Below are a number of tasks. Needless to say, please read these carefully and make sure you do not miss parts of an assignment!
Most assignments require coding and descriptions with motivations of what you have done. Most of the descriptions should be in the form of Haddock comments for modules and fuctions. There are also several questions in this description, make sure you answer all of them in your report.
The first part of this assignment is just to get you started. You should get started on Part II before the deadline for Part I.
Download and unpack this stub cabal package or the OpenGL/GLUT version. The archive contains a file structure and some useful code snippets for you to build your implementation on. You are free to modify the package however you wish or build a new package from scratch, but if you deviate significantly from the structure in the stub file you may want to explain why your version is an improvement.
Make sure "cabal configure", "cabal build" and "cabal haddock" works (switch the contents of the files to match the graphics framework you are using if not HGL). Look through the contents of the package and the generated documentation, run the generated executable and make sure it works. Fill in or replace the fields in the .cabal file with the appropriate information.
Familiarise yourself with the graphics library you are using by writing a simple program which opens a window and draws something in it (the provided code already does this for HGL, but make sure you understand how it is used).
The turtle graphics language should be implemented as an embedded language library in Haskell. The turtle language should be provided to the user as a single module, exporting abstract datatypes and operations.
An important part of creating an embedded language is to think carefully about what interface you want to offer to the user. Think about compositionality (how easy is it to combine simpler programs to build more complex ones?), and abstraction (hiding irrelevant implementation details from the users).
For instance, your library might define and export the following things (different types are conceivable, as long as they implement the same basic functionality):
type Program -- the abstract type of a turtle program forward :: Double -> Program right :: Double -> Program
Other turtle commands you should provide are:
pendown: stop drawing and start drawing respectively,
color: changes the color of the turtle's pen,
die: "kills" the turtle (rendering it unable to perform any more actions),
idle: a program that does nothing,
limited: makes the turtle stop what it is doing after a specified period of time (by some definition of time not directly related to minutes and seconds),
lifespan: kills the turtle after a specified period of time,
left: self explanatory,
times: repeats a turtle program a certain number of times, and
forever: that repeats a program forever.
You will also need a sequencing operator
(>*>) to perform commands one after
For running programs, in addition to the graphical interface your program should provide a simple textual interface that prints what happens in sequential order, i.e., prints a description of the actions that the turtles perform at each "step" in time (which lines are drawn, what action turtles perform or any other representation of what is going on will suffice). The textual interface needs to be productive for infinite turtle programs like those created with forever (it should go on printing the actions indefinitely). The graphical interface only needs to handle infinite programs for grades 4 or 5.
Write down the interface of your library, like in the example above, listing the types you plan to export (you may sketch definitions for them, but it is not needed for this part) and type signatures for the operations. When appropriate add explanations of what your operations are intended to do. Make sure you do not forget to add at least one run function for your programs (the types of the run functions may be a bit sketchy at this stage, but explain what each of them do in their comments).
Write down the spiral example from the Logo programming language using your interface. In their syntax it looks like:
to spiral :size :angle if :size > 100 [stop] forward :size right :angle spiral :size + 2 :angle end
You will not be able to run your spiral example yet, but it should type check. Also, make one version of the spiral example that goes on forever.
Finally make a program that would draw a finite spiral and when done starts drawing an infinite spiral where the finite spiral ended.
In this part of the assignment you will implement your turtle language and write a report.
Implement the library you designed in Part I. Clearly separate primitive and derived operations, and try to keep the set of primitive operations as small as possible.
At this point you might realise that the interface you have designed is missing some operations, or that parts of it are difficult to implement. Do not hesitate to change the interface in these cases--just be clear about what you changed and motivate why the change was needed (in your report or in the documentation for the relevant functions).
Make sure that you carefully define the borders of your library by exporting only the things you want a user to see (the interface you have designed).
Add a parallel composition combinator to your turtle language. One possible interface parallel composition could have is:
(<|>) :: Program -> Program -> Program
When you run a turtle program
p <|> q, there will be two turtles, one running
p and the other running
q, in parallel. In your textual interface you should
show which actions occur in parallel. Importantly, it must be the case that
(<|>) could potentially be applied in any part of your program.
Add a new module TurtleExtras to your cabal package. This module should contain some derived operators that you think may be a useful addition to the language. Try to add higher level components like squares and other geometrical shapes as well as operators that capture common patterns.
Operators that demonstrate the flexibility of your turtle language are encouraged. See the grading section for more hints.
Implement a few examples that together use all of the constructs you have implemented. (Do this in a different module that imports the module defining the embedded language.) Make sure that you have at least one program that does not terminate, and show that the textual interface can handle this.
Please, choose your "favorite" turtle program, resulting in a cool picture or animation. This program should be the main function of your executable module, so building your cabal package yields an executable that runs the program in graphical mode.
Please, address all the following points:
Start by answering all the questions in the assignment description above.
Did you use a shallow or a deep embedding, or a combination? Why? Discuss in a detailed manner how you would have implemented the Program type if you had chosen the other approach. What would have been easier/more difficult?
Compare the usability of your embedding against a custom-made implementation of a turtle language with dedicated syntax and interpreters. How easy is it to write programs in your embedded language compared to a dedicated language? What are the advantages and disadvantages of your embedding?
Compare the ease of implementation of your embedding against a custom-made implementation. How easy was it to implement the language and extensions in your embedded language compared to a dedicated language? What are the advantages/disadvantages of your embedding?
In what way have you used the following programming language features: higher-order functions, laziness, polymorphism?
Required only for grade 5 (but helpful for grade 4): Characterize the relationships between your operators as a set of algebraic laws. For inspiration look at the laws of algebraic semirings, also look at the laws for the monoid typeclassand possibly other type classes like Applicative/Alternative. Also, consider the following:
- How do
- How do idle and die interact with your combinators?
- How do forever and times interact with your combinators?
- Can you find any law that is unexpected, unintuitive or undesired, but follows from your operators?
- The report should be well structured. Avoid adding trivial or misleading/incorrect information to your report.
- The report should be self-contained. When answering a question, include it in the text. When referring to a piece of code, include it. If it is too lengthy, refer to exactly where it is.
- Using a consistent style for code is nice. Easily readable code is not the most important aspect of this lab but it does make a difference. Consider using HLint for suggestions on better readability of your code.
- Use Hoogle or similar to search for the type signature of a function you need before implementing it, chances are you just need to import another module.
- Short and understandable code is best. Long and understandable code is better than short and obfuscated.
Before you submit your code, clean it up! Submitting clean code is really important, and simply the polite thing to do. After you feel you are done, spend some time on cleaning your code; make it simpler, remove unnecessary things, etc. We will reject your solution if it is not clean. Clean code:
- Does not have long lines (< 80 characters)
- Has a consistent layout
- Has type signatures for all top-level functions
- Has good comments for all modules, functions, data types and instances. The comments should look good when compiled to HTML with Haddock.
- Has no junk (junk is unused code, commented code, unnecessary comments)
- Has no overly complicated function definitions
- Does not contain any repetitive code (copy-and-paste programming)
- For Part I the submission format is not so important, use the stub cabal package or just submit one or two .hs files if you prefer -- as long as they contain appropriate commenting (you do not need to write any functions except the example for the first part, just type signatures). Make sure to answer the question asked in part one about defining the finite spiral using the infinite one! Omitting this is one of the few ways to fail on the first part.
- For Part II, your submission needs to include the following:
- Your cabal package, containing your solution. Use
cabal sdistto generate the source tarball. Make sure the tarball is working by extracting it in another directory and running
cabal haddockand checking that everything looks right.
report.pdf, which contain documentation about what you have done. Please, give the motivations you were asked to give in the assignments, answers to questions, and how to use your language.
- Your cabal package, containing your solution. Use
One of the focuses for this lab is DSL design. This means that it is difficult to formulate precise requirements for each grade without solving the problem for you (by telling you which design to use).
This does not mean that we judge your submission arbitrarily. If you come and talk to us during office hours we can give you more precise descriptions of how you need to improve your submission for a higher grade.
Here are some qualities that count towards higher grades:
- The data types you use should not be needlessly complicated. The intended use of the data type should preferably be self-explanatory to someone who is familiar with the problem you are solving.
- Few primitive operations is usually good (although this can be taken to an extreme). If an operator can easily be defined using other primitive operators, it should be.
- Separation of concerns is good. For instance if you need to consider the existence of the lifespan operation when you code some other feature you may be doing something wrong.
- The examples should be interesting. Programs that use the advantages of EDSLs are encouraged, i.e. you should use your knowledge of Haskell to make more interesting programs using less code.
- The (optional) "additional operators" you define should demonstrate the flexibility of your language. Show for instance how a general operator can be defined and used to define different useful programs. Try to think of patterns that may recur in turtle programs and define operators to make them more concise. Think of useful higher order programs (programs that take programs as parameters).
- The parallel operator should fit well into your language. Its behaviour should be easy to describe without lots of special cases and arbitrary choices.
If you do not undestand what is meant by any of these we encourage you to ask us during office hours.
Requirement for grade 4
- You need to implement the TurtleExtras module.
- The graphical interface must handle infinite programs.
Requirement for grade 5
- The relationships between your operators and some relevant algebraic laws must be defined as a list of equations. You do not need to prove or test the equalities, but the more obvious they are from your code the better.
- The behavior of the parallel operator is clearly defined and contrasted with other alternatives.