(Somewhat) Algebraic Data Types for Racket
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
experimental experimenting w/ polymorphic stuff Dec 21, 2015
private Update details.md Apr 29, 2015
.gitignore fixed type-case bug Apr 22, 2015
LICENSE Initial commit Apr 21, 2015
README.md Update README.md May 24, 2017
info.rkt Remove unused dependency. Sep 8, 2015
main.rkt setup as a package Apr 23, 2015

README.md

datatype

Algebraic(-like) Data Types for Racket!

(Well... just Typed Racket at the moment...)

Big thanks to Andre Kuhlenschmidt (https://github.com/akuhlens) for patiently helping me work out the macro details!

This package should not yet be considered 'stable' -- I just finished the first implementation on 23 April and if I see something that needs fixed I may break backwards compatibility for the sake of making the feature better/correct.

Installation & Updating

  • 'raco pkg install datatype' & 'raco pkg update datatype' from the terminal
  • or via the package manager in Dr Racket

Bug reports welcome!

Goals

  1. Construct a datatype similar to those found in ML/Haskell (i.e. algebraic datatypes)
  2. Highly prefer using a match/case-like construct instead of more traditional type-predicates + accessors, and provide static verification of case coverage (explicitly or with an else)
  3. Users cannot extend these types outside of their initial declaration.

Example

Defining a new datatype:

(require datatype)

(define-datatype Expr
  [Var (Symbol)]
  [Lambda (Symbol Expr)]
  [App (Expr Expr)])

Using a type-case with a clause for each variant:

(: foo (Expr -> Symbol))
(define (foo e)
  (type-case Expr e
    [(Var x) => x]
    [(Lambda y _) => y]
    [(App _ _) => 'app]))

Using a type-case with an else:

(: bar (Expr -> Symbol))
(define (bar e)
  (type-case Expr e
    [(Var x) => x]
    [(Lambda y b) => y]
    [else => (error 'foo "App not supported: ~a" e]))

An else clause can be used as the last clause.

Programmer must provide exactly one case for each variant XOR provide an else clause. A compile time error will warn a programmer when this requirement is not met.

Technical Details

The example datatype expands roughly into this:

(struct Expr () #:transparent)
(struct Var ([ν : Symbol) #:transparent)
(struct Lambda ([ν : Symbol] [ν : Expr]) #:transparent)
(struct App ([ν : Expr] [ν : Expr]) #:transparent)

This datatype defines three struct types which all inherit from the Expr struct.

Expr structs (i.e. the empty parent class) cannot be created or extended (either will result in a run-time error when an offending instance is created).

The children structs (Var, Lambda, and App) cannot be extended (results in a run-time error when an offending instance is created).

The field names, ν, are all gensym'd to deter the usage of accessors functions.

Future work...?

At the moment just the basics are implemented. It might be nice to also have:

  • Support in regular Racket!
  • easy recursive typecasing on subelements (while maintaining statically verified coverage)
  • option to name struct arguments and generate accessors using those names
  • ability to typecase on multiple datatype values at once (a la Racket's match*)
  • Ability to add regular types to a datatype? (We would have to switch from struct inheritance to a macro-generated union type for datatypes using this approach...)
  • ... any suggestions