On this page:
2.1.1 Type Annotation
:
2.1.2 Definitions
def
defn
2.1.3 Anonymous Functions
lambda
λ
lambda*
λ*
2.1.4 Local Bindings
let
letrec
2.1.5 Pattern Matching
case
case*
pattern
2.1.6 Imports
require

2.1 Core Syntactic Forms

2.1.1 Type Annotation

syntax

(: expr type)

Annotates expr with type. If expr does not have the given type, a type error is produced.

Examples:
> (: 42 Integer)

: Integer

42

> (: "hello" String)

: String

"hello"

> (: "hello" Integer)

eval:4:0: typechecker: type mismatch

  between: String

      and: Integer

  in: "hello"

Additionally, some forms (such as def and defn) recognize literal uses of : to provide additional locations for type annotations.

2.1.2 Definitions

syntax

(def id maybe-type maybe-fixity-ann val-expr)

 
maybe-type = : type
  | 
     
maybe-fixity-ann = #:fixity fixity
  | 
     
fixity = left
  | right
Hackett’s basic definition form. Defines a new binding named id with the value val-expr. If type is provided, it is used as the type for id, and val-expr’s type is checked against the annotation. Otherwise, a type for id is inferred from val-expr.

Examples:
> (def x 7)
> x

: Integer

7

> (def y : Integer 7)
> (def z : String 7)

eval:6:0: typechecker: type mismatch

  between: Integer

      and: String

  in: 7

If fixity is specified, it defines id’s operator fixity when used as an infix operator.

Examples:
> (def -/right #:fixity right -)
> {10 -/right 15 -/right 6}

: Integer

1

syntax

(defn id maybe-type maybe-fixity-ann
  [[arg-pat ...+] body-expr] ...+)
 
maybe-type = : type
  | 
     
maybe-fixity-ann = #:fixity fixity
  | 
     
fixity = left
  | right
A shorthand definition form for defining a function with multiple cases. Essentially equivalent to the following combination of def, lambda, and case*:

(def id maybe-type
  (lambda [arg ...]
    (case* [arg ...]
      [[arg-pat ...] body-expr] ...)))

The defn form is generally preferred when defining top-level functions.

Examples:
> (defn square : {Integer -> Integer}
    [[x] {x * x}])
> (square 5)

: Integer

25

2.1.3 Anonymous Functions

syntax

(lambda [arg-pat ...+] body-expr)

syntax

(λ [arg-pat ...+] body-expr)

Produces a curried function. The curried arity of the produced function will be the same as the number of arg-pats provided. When the function is applied, the provided arguments will be matched against each arg-pat, and the function’s result will be body-expr.

Example:
> ((λ [x y] {x + {y * 2}}) 5 10)

: Integer

25

syntax

(lambda* [[arg-pat ...+] body-expr] ...+)

syntax

(λ* [[arg-pat ...+] body-expr] ...+)

Produces a curried function with multiple cases. Essentially equivalent to the following combination of lambda and case*:

(lambda [arg ...]
  (case* [arg ...]
    [[arg-pat ...] body-expr] ...))

Example:
> ((λ* [[(Just x)] x]
       [[Nothing] 0])
   (Just 42))

: Integer

42

2.1.4 Local Bindings

syntax

(let ([id maybe-type val-expr] ...+)
  body-expr)
 
maybe-type = : type
  | 
Defines local bindings, bindings which are only available within the syntactic extent of the let block. Each id is bound to the result of val-expr, from top to bottom. Each id is in scope in subsequent val-exprs, and all ids are in scope in body-expr (unless shadowed by another binding).

Examples:
> (let ([x 10])
    x)

: Integer

10

> (let ([x 10]
        [y (+ x 1)])
    y)

: Integer

11

> (let ([x 10]
        [y {x + 1}]
        [z {y * 2}])
    z)

: Integer

22

syntax

(letrec ([id maybe-type val-expr] ...+)
  body-expr)
 
maybe-type = : type
  | 
Like let, but the bindings created are potentially mutually-recursive. Each id is bound to the result of val-expr, and each id is in scope within each val-expr as well as the body-expr. Unlike let, all ids must be unique.

This allows val-exprs to refer to themselves (to create circular values or recursive functions) or other bindings (to create shared or mututally recursive values or functions).

Examples:
> (letrec ([xs {1 :: xs}])
    (take 5 xs))

: (List Integer)

{1 :: 1 :: 1 :: 1 :: 1 :: Nil}

> (letrec ([xs {1 :: ys}]
           [ys {2 :: xs}])
    (take 5 xs))

: (List Integer)

{1 :: 2 :: 1 :: 2 :: 1 :: Nil}

2.1.5 Pattern Matching

syntax

(case val-expr
  [pat body-expr] ...+)
Matches val-expr against each pat, in order. The result of the expression is the result of the body-expr for the first matching pat.

Example:
> (case (Just 42)
    [(Just x) x]
    [Nothing 0])

: Integer

42

syntax

(case* [val-expr ...+]
  [[pat ...+] body-expr] ...+)
Like case, but matches against multiple values at once. Each case only succeeds if all pats succeed.

Example:
> (case* [(Just 1) (Just 2)]
    [[(Just _) Nothing] "first"]
    [[Nothing (Just _)] "second"]
    [[(Just _) (Just _)] "both"]
    [[Nothing Nothing] "neither"])

: String

"both"

syntax

(pattern (name-id param-id ...) body-pattern/expr)

Defines name-id as both a function and a pattern, such that (name-id arg ...) is equivalent to body-pattern/expr with each occurrence of param-id substituted with the corresponding arg.

The body-pattern/expr form will be interpreted in two different ways—as a pattern or as an expression—depending on the context within which it is used. The pattern (name-id arg-id ...) will consistently match the value produced by (name-id arg-expr ...), assuming every pattern used in body-pattern/expr also has this property.

Examples:
> (data (Exp* e)
    (Var* String)
    (App* e e)
    (Lam* String e)
    #:deriving [Show])
> (data Exp
    (E (Exp* Exp))
    #:deriving [Show])
> (pattern (Var x)   (E (Var* x)))
> (pattern (App a b) (E (App* a b)))
> (pattern (Lam x a) (E (Lam* x a)))
> (defn free : {Exp -> (List String)}
    [[(Var x)] (List x)]
    [[(App f a)] {(free f) ++ (free a)}]
    [[(Lam x b)] (filter (/= x) (free b))])
> (free (Var "x"))

: (List String)

{"x" :: Nil}

> (free (App (Lam "x" (App (Var "x") (Var "y")))
             (Lam "z" (App (Var "a") (Var "z")))))

: (List String)

{"y" :: "a" :: Nil}

2.1.6 Imports

syntax

(require require-spec ...)

Imports bindings from a module, just like require from racket/base. The require binding provided by hackett adds support for properly importing bindings in the type namespace, but otherwise, the behavior is the same.