A tiny language, a subset of Haskell (with type classes) aimed at aiding teachers teach Haskell
Running code in Duet literally performs one substitution step at
time. For example, evaluating
(\x -> x + 5) (2 * 3), we get:
$ duet run demo.hs (\x -> x + 5) (2 * 3) (2 * 3) + 5 6 + 5 11
Note that this demonstrates basic argument application and non-strictness.
Run with the docker distribution, to easily run on any platform:
$ docker run -it -v $(pwd):/w -w /w chrisdone/duet run foo.hs
(This should work on Linux, OS X or Windows PowerShell.)
The image is about 11MB, so it's quick to download.
Differences from Haskell
See also the next section for a complete example using all the available syntax.
- Duet is non-strict, but is not lazy. There is no sharing and no thunks.
importmodule system whatsoever.
letsyntax, no parameters in definitions e.g.
f x = ..you must use a lambda. Representing
letin the stepper presents a design challenge not currently met.
class Functor (f :: Type -> Type).
- Kind inference is not implemented, so if you want a kind other than
*in Haskell), you have to put a kind signature on the type variable.
- Indentation is stricter, a case's alts must be at a column larger
- Duet does not have
seq, but it does have bang patterns in cases.
case x of !x -> ..is a perfectly legitimate way to force a value.
- Infix operators are stricter: an infix operator must have spaces
around it. You cannot have more than one operator without
parentheses, therefore operator precedence does not come into play
in Duet (this is intentional). This also permits you to write
-5without worrying about where it rests.
- Superclasses are not supported.
- Operator definitions are not supported.
- There is only
Rationalnumber types: they are written as
_foomeans "hole" and the interpreter does not touch them, it continues performing rewrites without caring. This is good for teaching.
- There is no standard
Prelude. The only defined base types are:
- You don't need a
Showinstance to inspect values; the interpreter shows them as they are, including lambdas.
examples/syntax-buffet.hs for an example featuring all the
syntax supported in Duet.
Print built-in types and classes
To print all types (primitive or otherwise), run:
$ duet types
data Bool = True | False data String data Integer data Rational
For classes and the instances of each class:
$ duet classes
class Num a where plus :: forall a. (a -> a -> a) times :: forall a. (a -> a -> a) instance Num Rational instance Num Integer class Neg a where negate :: forall a. (a -> a -> a) subtract :: forall a. (a -> a -> a) abs :: forall a. (a -> a) instance Neg Rational instance Neg Integer class Fractional a where divide :: forall a. (a -> a -> a) recip :: forall a. (a -> a) instance Fractional Rational class Monoid a where append :: forall a. (a -> a -> a) empty :: forall a. a instance Monoid String class Slice a where drop :: forall a. (Integer -> a -> a) take :: forall a. (Integer -> a -> a) instance Slice String
Strings are provided as packed opaque literals. You can unpack them
class Slice a where drop :: Integer -> a -> a take :: Integer -> a -> a
You can append strings using the
class Monoid a where append :: a -> a -> a empty :: a
String type is an instance of these classes.
main = append (take 2 (drop 7 "Hello, World!")) "!"
Evaluates strictly because it's a primop:
append (take 2 (drop 7 "Hello, World!")) "!" append (take 2 "World!") "!" append "Wo" "!" "Wo!"
You can use this type and operations to teach parsers.
Basic terminal input/output is supported.
$ duet run examples/terminal.hs --hide-steps Please enter your name: Chris Hello, Chris
And with steps:
$ duet run examples/terminal.hs PutStrLn "Please enter your name: " (GetLine (\line -> PutStrLn (append "Hello, " line) (Pure 0))) Please enter your name: GetLine (\line -> PutStrLn (append "Hello, " line) (Pure 0)) Chris (\line -> PutStrLn (append "Hello, " line) (Pure 0)) "Chris" PutStrLn (append "Hello, " "Chris") (Pure 0) Hello, Chris Pure 0
How does this work? Whenever the following code is seen in the stepper:
PutStrLn "Please enter your name: " <next>
The string is printed to stdout with
putStrLn, and the
expression is stepped next.
Whenever the following code is seen:
GetLine (\line -> <next>)
The stepper runs
getLine and feeds the resulting string into the
(\line -> <next>) "The line"
This enables one to write an example program like this:
data Terminal a = GetLine (String -> Terminal a) | PutStrLn String (Terminal a) | Pure a main = PutStrLn "Please enter your name: " (GetLine (\line -> PutStrLn (append "Hello, " line) (Pure 0)))