A tiny language, a subset of Haskell aimed at aiding teachers teach Haskell
Haskell
216
494 commits
updated May 7, 2023
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.
See also the next section for a complete example using all the available syntax.
module or import module system whatsoever.let syntax, no parameters in definitions e.g. f x = .. you
must use a lambda. Representing let in the stepper presents a
design challenge not currently met.* are written Type: e.g. class Functor (f :: Type -> Type).Type (aka * in Haskell), you have to put a kind signature on the
type variable.case.seq, but it does have bang patterns in
cases. case x of !x -> .. is a perfectly legitimate way to force a
value.-5
without worrying about where it rests.Integer and Rational number types: they are
written as 1 or 1.0._ or _foo means "hole" and the interpreter does not touch
them, it continues performing rewrites without caring. This is good
for teaching.Prelude. The only defined base types are:
Show instance to inspect values; the interpreter
shows them as they are, including lambdas.View examples/syntax-buffet.hs for an example featuring all the
syntax supported in Duet.
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
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.
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)))
494 commits
Haskell
99.7%
A tiny language, a subset of Haskell aimed at aiding teachers teach Haskell
Haskell
216
494 commits
updated May 7, 2023
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.
See also the next section for a complete example using all the available syntax.
module or import module system whatsoever.let syntax, no parameters in definitions e.g. f x = .. you
must use a lambda. Representing let in the stepper presents a
design challenge not currently met.* are written Type: e.g. class Functor (f :: Type -> Type).Type (aka * in Haskell), you have to put a kind signature on the
type variable.case.seq, but it does have bang patterns in
cases. case x of !x -> .. is a perfectly legitimate way to force a
value.-5
without worrying about where it rests.Integer and Rational number types: they are
written as 1 or 1.0._ or _foo means "hole" and the interpreter does not touch
them, it continues performing rewrites without caring. This is good
for teaching.Prelude. The only defined base types are:
Show instance to inspect values; the interpreter
shows them as they are, including lambdas.View examples/syntax-buffet.hs for an example featuring all the
syntax supported in Duet.
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
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.
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)))
494 commits
Haskell
99.7%