Home

Awesome

<img src=images/duet.svg height=36> Duet

A tiny language, a subset of Haskell (with type classes) aimed at aiding teachers teach Haskell

Run

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.

Docker run

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.

View 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

Example output:

data Bool
  = True
  | False
data String
data Integer
data Rational

For classes and the instances of each class:

$ duet classes

Example output:

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

String operations

Strings are provided as packed opaque literals. You can unpack them via the Slice class:

class Slice a where
  drop :: Integer -> a -> a
  take :: Integer -> a -> a

You can append strings using the Monoid class:

class Monoid a where
  append :: a -> a -> a
  empty :: a

The 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.

I/O

Basic terminal input/output is supported.

For example,

$ 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 next expression is stepped next.

Whenever the following code is seen:

GetLine (\line -> <next>)

The stepper runs getLine and feeds the resulting string into the stepper as:

(\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)))