On this page:
?
type
is_  a
:  :

3 Types🔗ℹ

Every Shplait expression has a type. The types come either from annotations or from Shplait’s type inference, which fills in missing annotations for expressions and bindings.

There are some built-in types like Int, some built-in type constructors like ->, and new types can be defined with type.

type

? id

A type variable, which stands for a type to be chosen later. For example, ?a -> ?a is the type of an identity function that accepts any value and returns the same value.

fun f(x :: ?a) :: Listof(?a):

  [x]

> f(1)

- Listof(Int)

[1]

> f("apple")

- Listof(String)

["apple"]

Using a type variable does not necessarily delay the choice indefinitely. In some cases, type inference will resolve a type variable to a concrete type.

fun addone(x :: ?a) :: ?b:

  x+1

> addone

- Int -> Int

#<function:addone>

declaration

type id maybe_type_args = as_type

 

declaration

type id maybe_type_args

| variant_id(field_id :: field_type, ...)

| ...

 

maybe_type_args

 = 

ϵ

 | 

(? of_id, ? of_id, ...)

Defines a new type, either id or id(arg_type, ...). A plain id type is defined when maybe_type_args is empty.

Using type with = defines id as an alias for as_type. If maybe_type_args is not empty, then as_type can refer to the arguments, and those references are replaced with the arg_types supplied when id(arg_type, ...) is used as a type. Any other type variables references in as_type are unified across all instantiations of the type alias.

type NumList = Listof(Int)

> def ns :: NumList = [1, 2, 3]

type Tagged(?a) = (Symbol * ?a)

> def now :: Tagged(Int) = values(#'time, 1200)

> def who :: Tagged(String) = values(#'name, "Alice")

When type is used with variant_id cases, each variant_id is defined as a constructor function, which takes arguments according to the field_id field declarations and produces a value of type id or id(arg_type, ...).

type Shape

| circle(radius :: Int)

| rectangle(width :: Int, height :: Int)

> circle(2)

- Shape

circle(2)

> rectangle(3, 4)

- Shape

rectangle(3, 4)

The normal way to dispatch on variants of a type and extract their components is using the match form. See its documentation for more examples:

def c = circle(2)

> match c

  | circle(r): 3*r*r

  | rectangle(w, h): w*h

- Int

12

As an alternative, is_a can identify a variant, and variant_id.field_id can be used as an accessor function. The accessor takes an instance of the variant and extracts the corresponding field value, and it raises an exception when applied to value (of an expression) of type id that is not an instance of variant_id.

> c is_a circle

- Boolean

#true

> circle.radius(c)

- Int

2

When a id(arg_type, ...) is defined with variant_ids, then id is a polymorphic type constructor, and the corresponding field-accessor functions are also polymorphic. These are polymorphic only to the degree that the variant field_types refer to the of_id type variables in maybe_type_args. Any other type variable that appears in a variant field_type is disallowed as a unguarded type variable.

type Treeof(?a)

| leaf(v :: ?a)

| node(left :: Treeof(?a), right :: Treeof(?a))

> node

- (Treeof(?a), Treeof(?a)) -> Treeof(?a)

#<function:node>

> node(leaf(1), leaf(2))

- Treeof(Int)

node(leaf(1), leaf(2))

> node(leaf("apple"), leaf("banana"))

- Treeof(String)

node(leaf("apple"), leaf("banana"))

expression

expr is_a variant_id

Returns #true if expr produces an instance constructed with variant_id, #false otherwise. The type of expr must be the same as the type produced by variant_id.

type Shape

| circle(radius :: Int)

| rectangle(width :: Int, height :: Int)

> circle(2) is_a circle

- Boolean

#true

> circle(2) is_a rectangle

- Boolean

#false

> "apple" is_a circle

typecheck failed: String vs. Shape

expression

expr :: type

The :: operator is normally used to declare a type for a field, variable binding, or function result, but it can also be used as an expression operator as long as the expression is within parentheses.

A :: expression in parentheses produces the same value as expr, but also asserts that expr has the type type. The type checker will report an error if the assertion does not hold.

Asserting a type is potentially useful for localizing type mismatches that otherwise span large portions of a program.

> 1 :: Int

op: needs to be in a type position or in parentheses

> (1 :: Int)

- Int

1

> (1 :: String)

typecheck failed: Int vs. String

> fun (x, y):

    // same type variable `a` forces same type for `x` and `y`

    values((x :: ?a), (y :: ?a))

- (?_a, ?_a) -> ?_a * ?_a

#<function:fun>