2.2 Datatypes
2.2.1 Defining algebraic datatypes
syntax
(data type-clause data-clause ... maybe-deriving)
type-clause = type-id | (type-constructor-id param-id ...+) maybe-fixity-ann | {param-id type-constructor-id param-id} maybe-fixity-ann data-clause = value-id | (data-constructor-id arg-type ...+) maybe-fixity-ann | {arg-type data-constructor-id arg-type} maybe-fixity-ann maybe-fixity-ann = #:fixity fixity |
fixity = left | right maybe-deriving = #:deriving [class-id ...] |
If type-clause is a bare type-id, then type-id is defined and bound directly to the freshly defined type. Alternatively, type-constructor-id may be provided, which binds type-constructor-id to a type constructor that accepts the same number of arguments as param-ids are provided and constructs the freshly defined type when fully saturated.
The fresh type is only inhabited by the values defined and produced by the specified data-clauses. Specifically, each value-id is bound to a unique value of the newly defined type. Similarly, each data-constructor-id is bound to a function that accepts arguments with types arg-types and constructs a value of the newly defined type that contains the provided values.
> (data Foo (Foo1 Integer Bool) (Foo2 String) Foo3 #:deriving [Show]) > (#:type Foo1) : {Integer -> Bool -> Foo}
> (Foo1 42 True)
: Foo
(Foo1 42 True)
> (#:type Foo2) : {String -> Foo}
> (Foo2 "hello")
: Foo
(Foo2 "hello")
> Foo3
: Foo
Foo3
Additionally, the bound value-ids and data-constructor-ids serve as patterns that match against different values of the defined type. The pattern associated with each data-constuctor-id accepts patterns that match against the contained values, so pattern-matching allows extracting values stored “inside” data constructors.
If #:deriving is provided, a typeclass instance on the defined type is derived for each of the typeclasses bound to each class-id using its deriving transformer. Specifically, for each class-id, a derive-instance form of the shape (derive-instance class-id type-id) is generated.
Like def, data supports operator fixity annotations. Each fixity specified controls the fixity used by the associated type-constructor-id or value-constructor-id when used as an infix operator.
> (data (Tree a) {(Tree a) :&: (Tree a)} #:fixity right (Leaf a))
> (instance (forall [a] (Show a) => (Show (Tree a))) [show (λ* [[{a :&: b}] {"{" ++ (show a) ++ " :&: " ++ (show b) ++ "}"}] [[(Leaf a)] {"(Leaf " ++ (show a) ++ ")"}])]) > {(Leaf 1) :&: (Leaf 2) :&: (Leaf 3)}
: (Tree Integer)
{(Leaf 1) :&: {(Leaf 2) :&: (Leaf 3)}}
2.2.2 Defining type aliases
syntax
(type type-clause maybe-fixity-ann type-expr)
type-clause = name-id | (name-id param-id ...+) maybe-fixity-ann = #:fixity fixity |
fixity = left | right
If param-ids are specified, then uses of the type alias must supply as many arguments as there are param-ids. The arguments are supplied like those to a type constructor—i.e. (name-id type-argument ...)—and the resulting type is type-expr with each param-id substituted with the corresponding type-argument.
> (type (Predicate a) {a -> Bool}) > (def zero? : (Predicate Integer) (== 0)) > (#:type zero?) : {Integer -> Bool}
> (zero? 0)
: Bool
True
> ((: zero? (Predicate Integer)) 0)
: Bool
True
Though the application of a type alias is syntactically similar to the application of a type constructor, type aliases are effectively type-level macros, and they may not be partially applied. All uses of a type alias must be fully saturated.
> (: zero? Predicate) eval:382.23: Predicate: expected 1 argument(s) to type alias
at: Predicate
in: Predicate
2.2.3 Numbers
syntax
value
value
value
value
value
value
value
syntax
value
value
value
value
2.2.4 Strings
syntax
value
string-length : {String -> Integer}
> (string-length "hello")
: Integer
5
> (string-length "Λάμβδα")
: Integer
6
> (string-split "," "1,2,3,4,5")
: (List String)
{"1" :: "2" :: "3" :: "4" :: "5" :: Nil}
> (string-split "," ",2,,4,")
: (List String)
{"" :: "2" :: "" :: "4" :: "" :: Nil}
> (string-split "," ",,,,")
: (List String)
{"" :: "" :: "" :: "" :: "" :: Nil}
value
> (string->bytes/utf-8 "αβγδ")
: Bytes
#"\316\261\316\262\316\263\316\264"
2.2.5 Bytes
syntax
value
bytes-length : {Bytes -> Bytes}
> (bytes-length #"abcd")
: Integer
4
> (bytes-length (string->bytes/utf-8 "αβγδ"))
: Integer
8
value
bytes->string/utf-8 : {Bytes -> (Maybe String)}
> (bytes->string/utf-8 #"\316\261\316\262\316\263\316\264")
: (Maybe String)
(Just "αβγδ")
> (bytes->string/utf-8 #"\303(")
: (Maybe String)
Nothing
2.2.6 Functions
syntax
(-> a b)
2.2.7 Quantification and Constrained Types
syntax
(forall [var-id ...+] type)
(forall [var-id ...+] constraint ...+ => type)
syntax
(∀ [var-id ...+] type)
(∀ [var-id ...+] constraint ...+ => type)
The second form is a shorthand that provides a nicer syntax for types constructed with => nested immediately within forall: (forall [var-id ...] constraint ... => type) is precisely equivalent to (forall [var-id ...] (=> [constraint ...] type)).
syntax
(=> [constraint ...+] type)
2.2.8 Unit
2.2.9 Booleans
Since Hackett is lazy, if is an ordinary function, not a macro or special form, and it can be used higher-order if desired.
> {True || True}
: Bool
True
> {False || True}
: Bool
True
> {True || False}
: Bool
True
> {False || False}
: Bool
False
> {True || (error! "never gets here")}
: Bool
True
> {True && True}
: Bool
True
> {False && True}
: Bool
False
> {True && False}
: Bool
False
> {False && False}
: Bool
False
> {False && (error! "never gets here")}
: Bool
False
2.2.10 The Identity Type
(require hackett/data/identity) | package: hackett-lib |
> (Identity 5)
: (Identity Integer)
(Identity 5)
> (map (+ 1) (Identity 5))
: (Identity Integer)
(Identity 6)
> {(Identity (+ 1)) <*> (Identity 5)}
: (Identity Integer)
(Identity 6)
> {(Identity "hello, ") ++ (Identity "world")}
: (Identity String)
(Identity "hello, world")
procedure
(run-identity x) → a
x : (Identity a)
> (run-identity (Identity 5))
: Integer
5
2.2.11 Tuples
2.2.12 Optionals
procedure
(from-maybe v x) → a
v : a x : (Maybe a)
> (from-maybe 0 (Just 5))
: Integer
5
> (from-maybe 0 Nothing)
: Integer
0
This type is generally used in a similar way to Maybe, but it allows the sort of failure to be explicitly tagged, usually returning a error message or failure reason on the Left side.
> (map (+ 1) (: (Right 5) (Either String Integer)))
: (Either String Integer)
(Right 6)
> (map (+ 1) (: (Left "an error happened") (Either String Integer)))
: (Either String Integer)
(Left "an error happened")
> (lefts {(Left 1) :: (Right "haskell") :: (Right "racket") :: (Left -32) :: Nil})
: (List Integer)
{1 :: -32 :: Nil}
> (rights {(Left 1) :: (Right "haskell") :: (Right "racket") :: (Left -32) :: Nil})
: (List String)
{"haskell" :: "racket" :: Nil}
> (partition-eithers {(Left 1) :: (Right "haskell") :: (Right "racket") :: (Left -32) :: Nil})
: (Tuple (List Integer) (List String))
(Tuple {1 :: -32 :: Nil} {"haskell" :: "racket" :: Nil})
2.2.13 Lists
syntax
(List element ...)
> (List 1 2 6 12 60)
: (List Integer)
{1 :: 2 :: 6 :: 12 :: 60 :: Nil}
List can also be used as a pattern:
> (head {1 :: 2 :: 3 :: Nil})
: (Maybe Integer)
(Just 1)
> (head (: Nil (List Integer)))
: (Maybe Integer)
Nothing
> (tail {1 :: 2 :: 3 :: Nil})
: (Maybe (List Integer))
(Just {2 :: 3 :: Nil})
> (tail (: Nil (List Integer)))
: (Maybe (List Integer))
Nothing
> (tail! {1 :: 2 :: 3 :: Nil})
: (List Integer)
{2 :: 3 :: Nil}
> (tail! (: Nil (List Integer))) tail!: empty list
> (take 2 {1 :: 2 :: 3 :: Nil})
: (List Integer)
{1 :: 2 :: Nil}
> (take 2 {1 :: Nil})
: (List Integer)
{1 :: Nil}
> (filter (λ [x] {x > 5}) {3 :: 7 :: 2 :: 9 :: 12 :: 4 :: Nil})
: (List Integer)
{7 :: 9 :: 12 :: Nil}
{x0 f {x1 f {x2 f .... {xn f acc} ....}}}
> (foldr + 0 {1 :: 2 :: 3 :: 4 :: 5 :: Nil})
: Integer
15
> (foldr * 1 {1 :: 2 :: 3 :: 4 :: 5 :: Nil})
: Integer
120
> (foldr - 0 {1 :: 2 :: 3 :: 4 :: 5 :: Nil})
: Integer
3
> (foldr :: Nil {1 :: 2 :: 3 :: 4 :: 5 :: Nil})
: (List Integer)
{1 :: 2 :: 3 :: 4 :: 5 :: Nil}
{.... {{{acc f x0} f x1} f x2} .... xn}
> (foldl + 0 {1 :: 2 :: 3 :: 4 :: 5 :: Nil})
: Integer
15
> (foldl * 1 {1 :: 2 :: 3 :: 4 :: 5 :: Nil})
: Integer
120
> (foldl - 0 {1 :: 2 :: 3 :: 4 :: 5 :: Nil})
: Integer
-15