Features
Helium
The language
The language is a subset of the
Haskell language.
The essential features we want to teach are all there: algebraic data types,
pattern matching, higher-order functions, laziness
and polymorphism.
The most notable difference with Haskell is the absence of full-blown overloading.
By means of a compiler flag, the programmer can disable or enable overloading.
If enabled, overloading is restricted to a number of well-known classes (Enum,Eq,Num,Ord,Show), and
all instances are derived. The compiler does not support syntax for defining classes or instances.
See
here for a full list of differences with Haskell.
The compiler
The compiler keeps track of a lot of information to produce
informative messages. For example, errors refer to
the exact location (line and column) of their origin and not just a
line number in the vicinity. The type inferencer uses a very
sophisticated algorithm to identify typing problems. Several kinds of
messages come with hints and suggestions for corrections.
Below you can read about some of the cool messages that are generated.
Speed of the compiler and of the generated code were never a priority, but the system
behaves reasonably well in both respects. The generated code
runs faster than Hugs, which was already
sufficient for teaching. And compilation of a typical lab
assignment (around 200 lines) takes less than a second on modern hardware.
The interpreter
Hint is a Java-based graphical interpreter.
It has a nice GUI with colours, toolbar buttons and menu's.
The best feature is that it integrates with your favourite editor and you
can jump to the exact locations of messages by clicking on them. Here is a screenshot:
Messages
The Helium system focuses on producing readable messages to the user.
Here are some highlights:
- Positions of error messages are exact. Not only a line number is given but a column, too. Other systems usually just show a line number and sometimes this does not even point to the problem but to the first line of the declaration containing the problem.
- Overloading can be turned off. This is an advantage when it comes to clear type errors, because the types are simpler. Instead of complaining about a missing Num instance in the expression 1+'a', we can say that 'a' was expected to be an Int.
- The compiler gives hints and suggests fixes. Based on our experience in the classroom we provide extra information when we encounter common mistakes. An example is misspelling the name of a function in one of its clauses.
Below are a few example programs with the output from the Helium compiler and a short explanation of each.
Confusing two functions
module Features1 where
main = [1,2,3] : [4, 5, 6]
(4,13): Type error in constructor
expression : :
type : a -> [a ] -> [a]
expected type : [Int] -> [Int] -> b
probable fix : use ++ instead
Note the following things:
- In the absence of overloading 1 is simply of type Int. Compilers for full Haskell refer to the Num class and use complicated terms like "Outstanding context" and "No instance for (Num [t])".
- Helium gives a hint telling you which operator can be used instead. It does this based on types and on a list of functions that are likely to be confused.
- The types in the messages are aligned so can you easily compare the component types yourself.
Misspelling an identifier
module Features2 where
main = concatmap (\x -> [f y]) "helium"
(3,8): Undefined variable "concatmap"
Hint: Did you mean "concatMap" ?
(3,26): Undefined variable "f"
(3,28): Undefined variable "y"
Note the following things:
- There is a hint telling you that even though the function you tried to apply does not exist, there is a function with a similar name.
- Exact positions are given.
- More than one error is given.
- Errors fit on one line (unless there is a hint)
Warnings
module Features3 where
main :: [Int]
main = myFilter even [1..10]
myFilter :: (a -> Bool) -> [a] -> [a]
myFilter p [] = []
myFIlter p (x:xs) =
if p x
then x : myFilter p xs
else myFilter p xs
(7,1), (8,1): Warning: Suspicious adjacent
functions "myFilter" and "myFIlter"
(7,10): Warning: Variable "p" is not used
(7,1): Warning: Missing pattern in function bindings:
myFilter _ (_ : _) = ...
(8,1): Warning: Missing type signature:
myFIlter :: (a -> Bool) -> [a] -> [a]
(8,1): Warning: Missing pattern in function bindings:
myFIlter _ [] = ...
Note the following things:
- Warnings about unused variables might point to real problems.
- The compiler also warns about missing type signatures. Writing type signatures is good practice and in this case it the warning is caused by a typo.
- The type in the warning is presented in such a way that you can copy and paste it into your program.
- The first warning points to a hard to find problem: making a typo in the name of a function in different clauses.
Type error hints
module Features4 where
main = f (0, 'a')
f :: (Char, Int) -> String
f (c, i) = [c] ++ showInt i
(3,10): Type error in tuple
expression : (0, 'a')
type : (Int , Char)
expected type : (Char , Int )
probable fix : re-order elements of tuple
Note the following things:
- Type errors can have hints telling you what you probably need to do to fix the problem.
- The types below each other are aligned.
- When overloading is disabled, there are different show functions for each type, like showInt, showString and showChar.
General heuristics
The type graph solver and many of the heuristics Helium provides are described in a paper presented at IFL '06
(see
Publications for more details).
The type inference directives
Type inference directives were described in a paper presented at ICFP '03 (see
Publications for more details).
It has been part of Helium ever since.
--
JurriaanHage - 14 Apr 2008