diff --git a/coalton.asd b/coalton.asd index 783b85438..12ec83b36 100644 --- a/coalton.asd +++ b/coalton.asd @@ -43,14 +43,14 @@ :serial t :components ((:file "set-float-traps") (:file "utils") - (:file "types") + (:coalton-file "types") (:file "primitive-types") (:file "classes") (:file "hash") (:file "builtin") - (:file "functions") - (:file "boolean") - (:file "bits") + (:coalton-file "functions") + (:coalton-file "boolean") + (:coalton-file "bits") (:module "math" :serial t :components ((:file "arith") @@ -65,15 +65,15 @@ (:file "dyadic") (:file "dual"))) (:file "randomaccess") - (:file "cell") + (:coalton-file "cell") (:file "tuple") (:file "iterator") (:file "optional") (:file "result") (:file "lisparray") (:file "list") - (:file "vector") - (:file "char") + (:coalton-file "vector") + (:coalton-file "char") (:file "string") (:file "slice") (:file "hashtable") diff --git a/library/bits.coal b/library/bits.coal new file mode 100644 index 000000000..0d756e3f0 --- /dev/null +++ b/library/bits.coal @@ -0,0 +1,25 @@ +(package coalton-library/bits + (shadow + and + or + xor + not) + (import-from + coalton-library/classes + Num) + (export + Bits + and + or + xor + not + shift)) + +(define-class (Num :int => Bits :int) + "Operations on the bits of twos-complement integers" + (and (:int -> :int -> :int)) + (or (:int -> :int -> :int)) + (xor (:int -> :int -> :int)) + (not (:int -> :int)) + (shift (Integer -> :int -> :int))) + diff --git a/library/boolean.coal b/library/boolean.coal new file mode 100644 index 000000000..7eb6c9aa8 --- /dev/null +++ b/library/boolean.coal @@ -0,0 +1,33 @@ +(package coalton-library/boolean + (import + coalton-library/classes + coalton-library/hash)) + +;; +;; Boolean instances +;; + +(define-instance (Hash Boolean) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Eq Boolean) + (define (== x y) + (lisp Boolean (x y) + (cl:eq x y)))) + +(define-instance (Ord Boolean) + (define (<=> x y) + (match x + ((True) + (match y + ((True) EQ) + ((False) GT))) + ((False) + (match y + ((True) LT) + ((False) EQ)))))) + +(define-instance (Default Boolean) + (define (default) False)) diff --git a/library/builtin.coal b/library/builtin.coal new file mode 100644 index 000000000..0ea32d6c7 --- /dev/null +++ b/library/builtin.coal @@ -0,0 +1,60 @@ +(package coalton-library/builtin + (import + coalton-library/classes) + (export + unreachable + undefined + error ; re-export from classes + not + xor + boolean-not + boolean-or + boolean-and + boolean-xor)) + +(lisp-toplevel () + (cl:eval-when (:compile-toplevel) + (cl:defmacro unreachable (cl:&optional (datum "Unreachable") cl:&rest arguments) + "Signal an error with CL format string DATUM and optional format arguments ARGUMENTS." + `(lisp :a () + (cl:error ,datum ,@arguments))))) + +(define (undefined _) + "A function which can be used in place of any value, throwing an error at runtime." + (error "Undefined")) + +(define not + "Synonym for `boolean-not`." + boolean-not) + +(define xor + "Synonym for `boolean-xor`." + boolean-xor) + +(declare boolean-not (Boolean -> Boolean)) +(define (boolean-not x) + "The logical negation of `x`. Is `x` false?" + (match x + ((True) False) + ((False) True))) + +(declare boolean-or (Boolean -> Boolean -> Boolean)) +(define (boolean-or x y) + "Is either `x` or `y` true? Note that this is a *function* which means both `x` and `y` will be evaluated. Use the `or` macro for short-circuiting behavior." + (match x + ((True) True) + ((False) y))) + +(declare boolean-and (Boolean -> Boolean -> Boolean)) +(define (boolean-and x y) + "Are both `x` and `y` true? Note that this is a *function* which means both `x` and `y` will be evaluated. Use the `and` macro for short-circuiting behavior." + (match x + ((True) y) + ((False) False))) + +(declare boolean-xor (Boolean -> Boolean -> Boolean)) +(define (boolean-xor x y) + "Are `x` or `y` true, but not both?" + (match x + ((True) (boolean-not y)) + ((False) y))) diff --git a/library/cell.coal b/library/cell.coal new file mode 100644 index 000000000..5f0008cdd --- /dev/null +++ b/library/cell.coal @@ -0,0 +1,141 @@ +(package coalton-library/cell + (import + coalton-library/builtin + coalton-library/classes) + (export + Cell + new + read + swap! + write! + update! + update-swap! + push! + pop! + increment! + decrement!)) + +(lisp-toplevel () + (cl:eval-when (:compile-toplevel :load-toplevel) + + (cl:declaim (cl:inline make-cell-internal)) + + (cl:defstruct cell-internal + (inner (cl:error "") :type cl:t)) + + (cl:defmethod cl:print-object ((self cell-internal) stream) + (cl:format stream "#.(CELL ~A)" (cell-internal-inner self)) + self) + + #+sbcl + (cl:declaim (sb-ext:freeze-type cell-internal)))) + +(repr :native cell-internal) +(define-type (Cell :a) + "Internally mutable cell") + +(declare new (:a -> Cell :a)) +(define (new data) + "Create a new mutable cell" + (lisp (Cell :a) (data) + (make-cell-internal :inner data))) + +(declare read (Cell :a -> :a)) +(define (read cel) + "Read the value of a mutable cell" + (lisp :a (cel) + (cell-internal-inner cel))) + +(declare swap! (Cell :a -> :a -> :a)) +(define (swap! cel data) + "Replace the value of a mutable cell with a new value, then return the old value" + (lisp :a (data cel) + (cl:let* ((old (cell-internal-inner cel))) + (cl:setf (cell-internal-inner cel) data) + old))) + +(declare write! (Cell :a -> :a -> :a)) +(define (write! cel data) + "Set the value of a mutable cell, returning the new value" + (lisp :a (data cel) + (cl:setf (cell-internal-inner cel) data))) + +(declare update! ((:a -> :a) -> Cell :a -> :a)) +(define (update! f cel) + "Apply F to the contents of CEL, storing and returning the result" + (write! cel (f (read cel)))) + +(declare update-swap! ((:a -> :a) -> Cell :a -> :a)) +(define (update-swap! f cel) + "Apply F to the contents of CEL, swapping the result for the old value" + (swap! cel (f (read cel)))) + +;;; operators on cells of lists +(declare push! (Cell (List :elt) -> :elt -> List :elt)) +(define (push! cel new-elt) + "Push NEW-ELT onto the start of the list in CEL." + (update! (Cons new-elt) cel)) + +(declare pop! (Cell (List :elt) -> Optional :elt)) +(define (pop! cel) + "Remove and return the first element of the list in CEL." + (match (read cel) + ((Cons fst rst) + (write! cel rst) + (Some fst)) + ((Nil) None))) + +;;; operators on cells of numbers +(declare increment! (Num :counter => Cell :counter -> :counter)) +(define (increment! cel) + "Add one to the contents of CEL, storing and returning the new value" + (update! (+ 1) cel)) + +(declare decrement! (Num :counter => (Cell :counter) -> :counter)) +(define (decrement! cel) + "Subtract one from the contents of CEL, storing and returning the new value" + (update! (+ -1) cel)) + + ;; i am very skeptical of these instances +(define-instance (Eq :a => Eq (Cell :a)) + (define (== c1 c2) + (== (read c1) (read c2)))) + +(define-instance (Ord :a => Ord (Cell :a)) + (define (<=> c1 c2) + (match (<=> (read c1) (read c2)) + ((LT) LT) + ((GT) GT) + ((EQ) EQ)))) + +(define-instance (Num :a => Num (Cell :a)) + (define (+ c1 c2) + (new (+ (read c1) (read c2)))) + (define (- c1 c2) + (new (- (read c1) (read c2)))) + (define (* c1 c2) + (new (* (read c1) (read c2)))) + (define (fromInt i) + (new (fromInt i)))) + +(define-instance (Semigroup :a => Semigroup (Cell :a)) + (define (<> a b) + (new (<> (read a) (read b))))) + +(define-instance (Functor Cell) + (define (map f c) + (new (f (read c))))) + +(define-instance (Applicative Cell) + (define pure new) + (define (liftA2 f c1 c2) + (new (f (read c1) (read c2))))) + +(define-instance (Into :a (Cell :a)) + (define into new)) + +(define-instance (Into (Cell :a) :a) + (define into read)) + +(define-instance (Default :a => Default (Cell :a)) + (define (default) (new (default)))) diff --git a/library/char.coal b/library/char.coal new file mode 100644 index 000000000..c21f5a72a --- /dev/null +++ b/library/char.coal @@ -0,0 +1,141 @@ +(package coalton-library/char + (import + coalton-library/classes + coalton-library/builtin + coalton-library/functions + coalton-library/hash + (coalton-library/iterator as iter)) + (export + char-code + char-code-unchecked + code-char + alpha? + ascii-alpha? + digit? + ascii-digit? + ascii-alphanumeric? + uppercase? + ascii-uppercase? + lowercase? + ascii-lowercase? + upcase + downcase + range)) + +(define-instance (Hash Char) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(declare char-code (Char -> UFix)) +(define (char-code char) + "Convert a character to its ASCII representation." + (lisp UFix (char) + (cl:char-code char))) + +(declare code-char-unchecked (UFix -> Char)) +(define (code-char-unchecked code) + "Convert a number to its ASCII character. This function is partial." + (lisp Char (code) + (cl:code-char code))) + +(declare code-char (UFix -> (Optional Char))) +(define (code-char code) + "Convert a number to its ASCII character, returning None on failure." + (lisp (Optional Char) (code) + ;; not sufficient to compare against `cl:char-code-limit', because the char-code space may be sparse. + (alexandria:if-let (char (cl:code-char code)) + (Some char) + None))) + +(define-instance (Eq Char) + (define (== x y) + (lisp Boolean (x y) (to-boolean (cl:char= x y))))) + +(define-instance (Ord Char) + (define (<=> x y) + (if (== x y) + EQ + (if (lisp Boolean (x y) (to-boolean (cl:char> x y))) + GT + LT)))) + +(declare alpha? (Char -> Boolean)) +(define (alpha? c) + "Is C an alphabetic character?" + (lisp Boolean (c) + (cl:alpha-char-p c))) + +(declare ascii-alpha? (Char -> Boolean)) +(define (ascii-alpha? c) + "Is C an ASCII alphabetic character?" + (lisp Boolean (c) + (cl:or + (cl:<= 65 (cl:char-code c) 90) + (cl:<= 97 (cl:char-code c) 122)))) + +(declare digit? (Char -> Boolean)) +(define (digit? c) + "Is C a digit character?" + (lisp Boolean (c) + (to-boolean (cl:digit-char-p c)))) + +(declare ascii-digit? (Char -> Boolean)) +(define (ascii-digit? c) + "Is C an ASCII digit character?" + (lisp Boolean (c) + (cl:<= 48 (cl:char-code c) 57))) + +(declare ascii-alphanumeric? (Char -> Boolean)) +(define (ascii-alphanumeric? c) + "Is C an ASCII alphanumeric character?" + (or (ascii-alpha? c) + (ascii-digit? c))) + +(declare uppercase? (Char -> Boolean)) +(define (uppercase? c) + "Is C an uppercase character?" + (lisp Boolean (c) + (cl:upper-case-p c))) + +(declare ascii-uppercase? (Char -> Boolean)) +(define (ascii-uppercase? c) + "Is C an ASCII uppercase character?" + (lisp Boolean (c) + (cl:or + (cl:<= 65 (cl:char-code c) 90)))) + +(declare lowercase? (Char -> Boolean)) +(define (lowercase? c) + "Is C a lowercase character?" + (lisp Boolean (c) + (cl:lower-case-p c))) + +(declare ascii-lowercase? (Char -> Boolean)) +(define (ascii-lowercase? c) + "Is C an ASCII lowercase character?" + (lisp Boolean (c) + (cl:or + (cl:<= 97 (cl:char-code c) 122)))) + +(declare upcase (Char -> Char)) +(define (upcase c) + "Returns the upcased version of C, returning C when there is none." + (lisp Char (c) + (cl:char-upcase c))) + +(declare downcase (Char -> Char)) +(define (downcase c) + "Returns the downcased version of C, returning C when there is none." + (lisp Char (c) + (cl:char-downcase c))) + +(declare range (Char -> Char -> iter:Iterator Char)) +(define (range start end) + "An inclusive range of characters from START to END by cl:char-code." + (iter:filter-map! + code-char + (iter:range-increasing + 1 + (char-code start) + (+ 1 (char-code end))))) diff --git a/library/classes.coal b/library/classes.coal new file mode 100644 index 000000000..4f2aa6aa8 --- /dev/null +++ b/library/classes.coal @@ -0,0 +1,340 @@ +(package coalton-library/classes + (import + (coalton-library/types as types)) + (export + Signalable + error + Tuple + Optional Some None + Result Ok Err + Eq == + Ord LT EQ GT + <=> > < >= <= + max + min + Num + - * fromInt + Semigroup <> + Monoid mempty + Functor map + Applicative pure liftA2 + Monad >>= + >> + MonadFail fail + Alternative alt empty + Foldable fold foldr mconcat + Traversable traverse + Bifunctor bimap map-fst map-snd + sequence + Into + TryInto + Iso + Unwrappable unwrap-or-else with-default unwrap expect as-optional + default defaulting-unwrap default?)) + +;;; +;;; Signaling errors and warnings +;;; + +;; +;; Signalling errors on supported types +;; +(define-class (Signalable :a) + "Signals errors or warnings by calling their respective lisp conditions." + (error "Signal an error with a type-specific error string." (:a -> :b))) + +(define-instance (Signalable String) + (define (error str) + (lisp :a (str) + (cl:error str)))) + +;; +;; Base Types +;; + +(define-struct (Tuple :a :b) + "A heterogeneous collection of items." + (first :a) + (second :b)) + +(define-type (Optional :a) + "Represents something that may not have a value." + (Some :a) + None) + +(define-type (Result :bad :good) + "Represents something that may have failed." + ;; We write (Result :bad :good) instead of (Result :good :bad) + ;; because of the limitations of how we deal with higher-kinded + ;; types; we want to implement Functor on this. + (Ok :good) + (Err :bad)) + +;; +;; Eq +;; + +(define-class (Eq :a) + "Types which have equality defined." + (== (:a -> :a -> Boolean))) + +(define-instance (Eq types:LispType) + (define (== a b) + (lisp Boolean (a b) + (cl:equalp a b)))) + +(define-class (Eq :a => Num :a) + "Types which have numeric operations defined." + (+ (:a -> :a -> :a)) + (- (:a -> :a -> :a)) + (* (:a -> :a -> :a)) + (fromInt (Integer -> :a))) + +(define-instance (Eq Unit) + (define (== _ _) True)) + +;; +;; Ord +;; + +(repr :enum) +(define-type Ord + "The result of an ordered comparison." + LT + EQ + GT) + +(define-instance (Eq Ord) + (define (== a b) + (match (Tuple a b) + ((Tuple (LT) (LT)) True) + ((Tuple (EQ) (EQ)) True) + ((Tuple (GT) (GT)) True) + (_ False)))) + +(define-instance (Ord Ord) + (define (<=> a b) + (match (Tuple a b) + ((Tuple (LT) (LT)) EQ) + ((Tuple (LT) (EQ)) LT) + ((Tuple (LT) (GT)) LT) + ((Tuple (EQ) (LT)) GT) + ((Tuple (EQ) (EQ)) EQ) + ((Tuple (EQ) (GT)) LT) + ((Tuple (GT) (LT)) GT) + ((Tuple (GT) (EQ)) GT) + ((Tuple (GT) (GT)) EQ)))) + +(define-class (Eq :a => Ord :a) + "Types whose values can be ordered." + (<=> (:a -> :a -> Ord))) + +(declare > (Ord :a => :a -> :a -> Boolean)) +(define (> x y) + "Is `x` greater than `y`?" + (match (<=> x y) + ((GT) True) + (_ False))) + +(declare < (Ord :a => :a -> :a -> Boolean)) +(define (< x y) + "Is `x` less than `y`?" + (match (<=> x y) + ((LT) True) + (_ False))) + +(declare >= (Ord :a => :a -> :a -> Boolean)) +(define (>= x y) + "Is `x` greater than or equal to `y`?" + (match (<=> x y) + ((LT) False) + (_ True))) + +(declare <= (Ord :a => :a -> :a -> Boolean)) +(define (<= x y) + "Is `x` less than or equal to `y`?" + (match (<=> x y) + ((GT) False) + (_ True))) + +(declare max (Ord :a => :a -> :a -> :a)) +(define (max x y) + "Returns the greater element of `x` and `y`." + (if (> x y) + x + y)) + +(declare min (Ord :a => :a -> :a -> :a)) +(define (min x y) + "Returns the lesser element of `x` and `y`." + (if (< x y) + x + y)) + + ;; + ;; Haskell + ;; + +(define-class (Semigroup :a) + "Types with an associative binary operation defined." + (<> (:a -> :a -> :a))) + +(define-class (Semigroup :a => Monoid :a) + "Types with an associative binary operation and identity defined." + (mempty :a)) + +(define-class (Functor :f) + "Types which can map an inner type where the mapping adheres to the identity and composition laws." + (map ((:a -> :b) -> :f :a -> :f :b))) + +(define-class (Functor :f => Applicative :f) + "Types which are a functor which can embed pure expressions and sequence operations." + (pure (:a -> (:f :a))) + (liftA2 ((:a -> :b -> :c) -> :f :a -> :f :b -> :f :c))) + +(define-class (Applicative :m => Monad :m) + "Types which are monads as defined in Haskell. See https://wiki.haskell.org/Monad for more information." + (>>= (:m :a -> (:a -> :m :b) -> :m :b))) + +(declare >> (Monad :m => (:m :a) -> (:m :b) -> (:m :b))) +(define (>> a b) + (>>= a (fn (_) b))) + +(define-class (Monad :m => MonadFail :m) + (fail (String -> :m :a))) + +(define-class (Applicative :f => Alternative :f) + "Types which are monoids on applicative functors." + (alt (:f :a -> :f :a -> :f :a)) + (empty (:f :a))) + +(define-class (Foldable :container) + "Types which can be folded into a single element." + (fold "A left tail-recursive fold." ((:accum -> :elt -> :accum) -> :accum -> :container :elt -> :accum)) + (foldr "A right non-tail-recursive fold."((:elt -> :accum -> :accum) -> :accum -> :container :elt -> :accum))) + +(declare mconcat ((Foldable :f) (Monoid :a) => :f :a -> :a)) +(define mconcat + "Fold a container of monoids into a single element." + (fold <> mempty)) + +(define-class (Traversable :t) + (traverse (Applicative :f => (:a -> :f :b) -> :t :a -> :f (:t :b)))) + +(declare sequence ((Traversable :t) (Applicative :f) => :t (:f :b) -> :f (:t :b))) +(define sequence (traverse (fn (x) x))) + +(define-class (Bifunctor :f) + "Types which take two type arguments and are functors on both." + (bimap ((:a -> :b) -> (:c -> :d) -> :f :a :c -> :f :b :d))) + +(declare map-fst (Bifunctor :f => (:a -> :b) -> :f :a :c -> :f :b :c)) +(define (map-fst f b) + "Map over the first argument of a `Bifunctor`." + (bimap f (fn (x) x) b)) + +(declare map-snd (Bifunctor :f => (:b -> :c) -> :f :a :b -> :f :a :c)) +(define (map-snd f b) + "Map over the second argument of a `Bifunctor`." + (bimap (fn (x) x) f b)) + + ;; + ;; Conversions + ;; + +(define-class (Into :a :b) + "`INTO` imples *every* element of `:a` can be represented by an element of `:b`. This conversion might not be bijective (i.e., there may be elements in `:b` that don't correspond to any in `:a`)." + (into (:a -> :b))) + +(define-class ((Into :a :b) (Into :b :a) => Iso :a :b) + "Opting into this marker typeclass imples that the instances for `(Into :a :b)` and `(Into :b :a)` form a bijection.") + +(define-instance (Into :a :a) + (define (into x) x)) + +(define-class (TryInto :a :b :c (:a :b -> :c)) + "`TRY-INTO` implies some elements of `:a` can be represented exactly by an element of `:b`, but sometimes not. If not, an error of type `:c` is returned." + (tryInto (:a -> (Result :c :b)))) + +(define-instance (Iso :a :a)) + + ;; + ;; Unwrappable for fallible unboxing + ;; + +(define-class (Unwrappable :container) + "Containers which can be unwrapped to get access to their contents. + +`(unwrap-or-else succeed fail container)` should invoke the `succeed` continuation on the unwrapped contents of +`container` when successful, or invoke the `fail` continuation with no arguments (i.e., with `Unit` as an argument) +when unable to unwrap a value. + +The `succeed` continuation will often, but not always, be the identity function. `as-optional` passes `Some` to +construct an `Optional`. + +Typical `fail` continuations are: +- Return a default value, or +- Signal an error." + (unwrap-or-else ((:elt -> :result) + -> (Unit -> :result) + -> (:container :elt) + -> :result))) + +(declare expect ((Unwrappable :container) => + String + -> (:container :element) + -> :element)) +(define (expect reason container) + "Unwrap `container`, signaling an error with the description `reason` on failure." + (unwrap-or-else (fn (elt) elt) + (fn () (error reason)) + container)) + +(declare unwrap ((Unwrappable :container) => + (:container :element) + -> :element)) +(define (unwrap container) + "Unwrap `container`, signaling an error on failure." + (unwrap-or-else (fn (elt) elt) + (fn () (error (lisp String (container) + (cl:format cl:nil "Unexpected ~a in UNWRAP" + container)))) + container)) + +(declare with-default ((Unwrappable :container) => + :element + -> (:container :element) + -> :element)) +(define (with-default default container) + "Unwrap `container`, returning `default` on failure." + (unwrap-or-else (fn (elt) elt) + (fn () default) + container)) + +(declare as-optional ((Unwrappable :container) => (:container :elt) -> (Optional :elt))) +(define (as-optional container) + "Convert any Unwrappable container into an `Optional`, constructing Some on a successful unwrap and None on a failed unwrap." + (unwrap-or-else Some + (fn () None) + container)) + + + ;; + ;; Default + ;; + +(define-class (Default :a) + "Types which have default values." + (default (Unit -> :a))) + +(declare defaulting-unwrap ((Unwrappable :container) (Default :element) => + (:container :element) -> :element)) +(define (defaulting-unwrap container) + "Unwrap an `unwrappable`, returning `(default)` of the wrapped type on failure. " + (unwrap-or-else (fn (elt) elt) + (fn () (default)) + container)) + +(declare default? ((Default :a) (Eq :a) => :a -> Boolean)) +(define (default? x) + "Is `x` the default item of its type?" + (== x (default))) diff --git a/library/functions.coal b/library/functions.coal new file mode 100644 index 000000000..f63a4df82 --- /dev/null +++ b/library/functions.coal @@ -0,0 +1,184 @@ +(package coalton-library/functions + (import + coalton-library/builtin + coalton-library/classes) + (export + trace + traceObject + print + unsafe-pointer-eq? + fix + id + const + flip + reduce + compose + conjoin + disjoin + complement + curry + uncurry + pair-with + msum + asum + /= + bracket)) + +(declare trace (String -> Unit)) +(define (trace str) + "Print a line to `cl:*standard-output*`." + (progn + (lisp :a (str) (cl:format cl:t "~A~%" str)) + Unit)) + +(declare traceObject (String -> :a -> Unit)) +(define (traceObject str item) + "Print a line to `cl:*standard-output*` in the form \"{STR}: {ITEM}\"." + (progn + (lisp :a (str item) (cl:format cl:t "~A: ~A~%" str item)) + Unit)) + +(declare print ((Into :a String) => :a -> Unit)) +(define (print item) + "Print the String representation of `item` to `cl:*standard-output*`." + (trace (into item))) + +(declare unsafe-pointer-eq? (:a -> :a -> Boolean)) +(define (unsafe-pointer-eq? a b) + (lisp Boolean (a b) + (to-boolean (cl:eq a b)))) + +;; +;; Function combinators +;; + +(declare fix (((:a -> :b) -> (:a -> :b)) -> (:a -> :b))) +(define (fix f n) + "Compute the fixed point of a unary function. This is equivalent to the Y-combinator of the lambda calculus. This combinator allows recursion without specific assignment of names. For example, the factorial function can be written + + (define fact + (fix + (fn (f n) + (if (== n 0) + 1 + (* n (f (- n 1)))))))" + (f (fix f) n)) + +(declare id (:a -> :a)) +(define (id x) + "A function that always returns its argument." + x) + +(declare const (:a -> :b -> :a)) +(define (const a _b) + "A function that always returns its first argument." + a) + +(declare flip ((:a -> :b -> :c) -> :b -> :a -> :c)) +(define (flip f x y) + "Returns a function that takes its arguments in reverse order." + (f y x)) + +(declare reduce (Foldable :f => (:a -> :b -> :b) -> :b -> (:f :a) -> :b)) +(define (reduce f y xs) + "The same as `fold` but with the argument order swapped to match `cl:reduce`" + (fold (flip f) y xs)) + +;; We don't write (COMPOSE F G X) even though it's OK so that the +;; most common case of using compose---as a binary function---is +;; considered to be "saturated". + +(declare compose ((:b -> :c) -> (:a -> :b) -> (:a -> :c))) +(define (compose f g) + "Produces a function equivalent to applying `g` followed by `f`." + ;; Note: ((compose f g) x) behaves like (f (g x)) + (fn (x) + (f (g x)))) + +(declare conjoin ((:a -> Boolean) -> (:a -> Boolean) -> :a -> Boolean)) +(define (conjoin f g x) + "Compute the conjunction of two unary Boolean functions." + (and (f x) (g x))) + +(declare disjoin ((:a -> Boolean) -> (:a -> Boolean) -> :a -> Boolean)) +(define (disjoin f g x) + "Compute the disjunction of two unary Boolean functions." + (or (f x) (g x))) + +(declare complement ((:a -> Boolean) -> :a -> Boolean)) +(define (complement f x) + "Compute the complement of a unary Boolean function." + (not (f x))) + +(declare curry ((Tuple :left :right -> :result) -> :left -> :right -> :result)) +(define (curry func left right) + "Take a function whose input is a tuple and enable curried application of the left and right parameters, equivalent to `(func (Tuple left right))`." + (func (Tuple left right))) + +(declare uncurry ((:left -> :right -> :result) -> Tuple :left :right -> :result)) +(define (uncurry func tpl) + "Take a function with two currying parameters and enable their input as a single `Tuple`." + (match tpl + ((Tuple left right) + (func left right)))) + +(declare pair-with ((:left -> :right) -> :left -> Tuple :left :right)) +(define (pair-with func left) + "Create a `Tuple` of the form `(Tuple left (func left))`." + (Tuple left (func left))) + +;; +;; Monadic operators +;; + +(declare msum ((Monoid :a) (Foldable :t) => :t :a -> :a)) +(define (msum xs) + "Fold over a list using `<>`." + (foldr <> mempty xs)) + +(declare asum ((Alternative :f) (Foldable :t) => :t (:f :a) -> :f :a)) +(define (asum xs) + "Fold over a list using `alt`." + (foldr alt empty xs)) + +(declare /= (Eq :a => :a -> :a -> Boolean)) +(define (/= a b) + "Is `a` not equal to `b`?" + (boolean-not (== a b))) + +;; +;; Instances +;; + +(define-instance (Functor (Arrow :a)) + (define map compose)) + +;;; +;;; Bracket pattern +;;; + +(lisp-toplevel () + (cl:eval-when (:compile-toplevel) + (cl:defmacro %unwind-protect (obj exit thunk) + "A wrapper on `cl:unwind-protect.`" + (cl:let ((output (cl:gentemp "OUTPUT"))) + `(cl:let (,output) + (cl:unwind-protect (cl:setq ,output (call-coalton-function ,thunk ,obj)) + (call-coalton-function ,exit ,obj)) + ,output))))) + +(declare bracket (Monad :m + => :m :a + -> (:a -> :m :b) + -> (:a -> :m :c) + -> :m :c)) +(define (bracket init exit body) + "Bracket takes an initial state, performs a body of operations, and then forces a safe exit. + +This wraps `cl:unwind-protect`. + +Modeled after Haskell: https://wiki.haskell.org/Bracket_pattern" + (do + (obj <- init) + (lisp (:m :c) (obj exit body) + (%unwind-protect obj exit body)))) diff --git a/library/functions.lisp b/library/functions.lisp index ec507983c..e766a9ef4 100644 --- a/library/functions.lisp +++ b/library/functions.lisp @@ -168,7 +168,7 @@ (cl:defmacro %unwind-protect (obj exit thunk) "A wrapper on `cl:unwind-protect.`" - (cl:let ((output (cl:gensym "OUTPUT"))) + (cl:let ((output (cl:gentemp "OUTPUT"))) `(cl:let (,output) (cl:unwind-protect (cl:setq ,output (call-coalton-function ,thunk ,obj)) (call-coalton-function ,exit ,obj)) diff --git a/library/hash.coal b/library/hash.coal new file mode 100644 index 000000000..c574ded90 --- /dev/null +++ b/library/hash.coal @@ -0,0 +1,84 @@ +(package coalton-library/hash + (import + coalton-library/classes) + (export + Hash + combine-hashes + combine-hashes-order-independent)) + +#+sbcl +(repr :native (cl:unsigned-byte 62)) + +#+allegro +(repr :native (cl:unsigned-byte 0 32)) + +;; https://github.com/Clozure/ccl/blob/ff51228259d9dbc8a9cc7bbb08858ef4aa9fe8d0/level-0/l0-hash.lisp#L1885 +#+ccl +(repr :native (cl:and cl:fixnum cl:unsigned-byte)) + +#+(not (or sbcl allegro ccl)) +#.(cl:error "hashing is not supported on ~A" (cl:lisp-implementation-type)) + +(define-type Hash + "Implementation dependent hash code") + +(define-class (Eq :a => Hash :a) + "Types which can be hashed for storage in hash tables. + +The hash function must satisfy the invariant that `(== left right)` implies `(== (hash left) (hash right))`." + (hash (:a -> Hash))) + +(declare combine-hashes (Hash -> Hash -> Hash)) +(define (combine-hashes lhs rhs) + (lisp Hash (lhs rhs) + ;; SBCL has a hash combination function + #+sbcl (sb-int:mix lhs rhs) + + ;; + ;; Generic hash combination functions copied from: + ;; https://stackoverflow.com/questions/5889238/why-is-xor-the-default-way-to-combine-hashes/27952689#27952689 + ;; + + ;; 32bit hash combination + #+allegro (cl:logxor lhs (cl:+ rhs #x9e3779b9 (cl:ash lhs 6) (cl:ash lhs -2))) + + ;; 64bit hash combination + ;; logand required on ccl to force the output to be a fixnum + #+ccl (cl:logand (cl:logxor lhs (cl:+ rhs #x517cc1b727220a95 (cl:ash lhs 6) (cl:ash lhs -2))) cl:most-positive-fixnum))) + +(declare combine-hashes-order-independent (Hash -> Hash -> Hash)) +(define (combine-hashes-order-independent lhs rhs) + (lisp Hash (lhs rhs) + (cl:logxor lhs rhs))) + +(define-instance (Eq Hash) + (define (== a b) + (lisp Boolean (a b) + (cl:= a b)))) + +(define-instance (Ord Hash) + (define (<=> a b) + (if (== a b) + EQ + (if (lisp Boolean (a b) (to-boolean (cl:> a b))) + GT + LT)))) + +(define-instance (Semigroup Hash) + (define (<> a b) + (combine-hashes a b))) + +(define-instance (Monoid Hash) + (define mempty + (lisp Hash () + 0))) + +(define-instance (Default Hash) + (define (default) + (lisp Hash () + 0))) + +(define-instance (Hash Hash) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) diff --git a/library/lisparray.coal b/library/lisparray.coal new file mode 100644 index 000000000..4b898084b --- /dev/null +++ b/library/lisparray.coal @@ -0,0 +1,64 @@ +;;;; An interface to Common Lisp rank-1 SIMPLE-ARRAYs. + +(package coalton-library/lisparray + (import + (coalton-library/types as types)) + (export + LispArray + make + make-uninitialized + length + aref + set!)) + +;; The representation of (LispArray :t) is specially dealt with by +;; the compiler in lisp-type.lisp. +(define-type (LispArray :t) + "A one-dimensional, non-resizable array of elements. + +These arrays are represented as possibly specialized `(cl:simple-array (cl:*))` and are meant to be used as a tool either to interface with Lisp code or to implement efficient data structures. One should consult `Vector` or `Seq` for more general sequential data structure needs. + +Whether or not the arrays are specialized depends on the underlying Lisp implementation. Consult `cl:upgraded-array-element-type` to determine whether `LispArray` may get specialized.") + +(declare make (types:RuntimeRepr :t => UFix -> :t -> LispArray :t)) +(define (make n x) + "Make a new `LispArray` of length `n` initialized to `x`. + +If the type of `x` represents a specialized array " + ;; FIXME: how can we get this statically? + (let ((type (types:runtime-repr (types:proxy-of x)))) + (lisp (LispArray :t) (n x type) + (cl:make-array n :element-type type :initial-element x)))) + +(declare make-uninitialized (types:RuntimeRepr :t => UFix -> LispArray :t)) +(define (make-uninitialized n) + "Make a new LispArray of length `n` that can store elements of type `:t`. + +WARNING: The consequences are undefined if an uninitialized element is read before being set. +" + (let p = types:Proxy) + (let p_ = (types:proxy-inner p)) + (let type = (types:runtime-repr p_)) + (types:as-proxy-of + (lisp (LispArray :t) (n type) + (cl:make-array n :element-type type)) + p)) + +(declare length (LispArray :t -> UFix)) +(define (length v) + "Return the length of the `LispArray` `v`." + (lisp UFix (v) + (cl:length v))) + +(declare aref (LispArray :t -> UFix -> :t)) +(define (aref v i) + "Read the `i`th value of the `LispArray` `v`." + (lisp :t (v i) + (cl:aref v i))) + +(declare set! (LispArray :t -> UFix -> :t -> Unit)) +(define (set! v i x) + "Set the `i`th value of the `LispArray` `v` to `x`." + (lisp Unit (v i x) + (cl:setf (cl:aref v i) x) + Unit)) diff --git a/library/math/arith.coal b/library/math/arith.coal new file mode 100644 index 000000000..9698815b8 --- /dev/null +++ b/library/math/arith.coal @@ -0,0 +1,192 @@ +;;;; Number types and basic arithmetic. + +(package coalton-library/math/arith + (import + coalton-library/builtin + coalton-library/classes + coalton-library/functions + coalton-library/utils) + (export + Reciprocable + / + reciprocal + Dividable + general/ + / + Transfinite + infinity + infinite? + finite? + negative-infinity + nan + nan? + negate + abs + sign + ash + 1+ + 1- + positive? + negative? + nonpositive? + nonnegative? + zero? + nonzero?)) + +;; +;; Division +;; + +(define-class (Num :a => Reciprocable :a) + "Any number with a multiplicative inverse (reciprocal) where: + + + 1 = (* (reciprocal x) x) = (* x (reciprocal x)) + (/ x y) = (* x (reciprocal y)) + + +If no reciprocal exists for an element, produce a run-time error (e.g., zero). +" + (/ (:a -> :a -> :a)) + (reciprocal (:a -> :a))) + +(define-class (Dividable :arg-type :res-type) + "The representation of a type such that division within that type possibly results in another type. For instance, + + + (Dividable Integer Fraction) + + +establishes that division of two `Integer`s can result in a `Fraction`, whereas + + + (Dividable Single-Float Single-Float) + + +establishes that division of two `Single-Float`s can result in a `Single-Float`. + +Note that `Dividable` does *not* establish a default result type; you must constrain the result type yourself. + +The function `general/` is partial, and will error produce a run-time error if the divisor is zero. +" + ;; This is a type that is more pragmatic and less mathematical in + ;; nature. It expresses a division relationship between one input + ;; type and one output type. + (general/ (:arg-type -> :arg-type -> :res-type))) + +(define-instance (Reciprocable :a => Dividable :a :a) + (define (general/ a b) (/ a b))) + +(define-class (Transfinite :a) + "Numeric type with a value for (positive) infinity and/or NaN." + (infinity :a) + (infinite? (:a -> Boolean)) + (nan :a) + (nan? (:a -> Boolean))) + +(declare finite? ((Transfinite :a) => :a -> Boolean)) +(define (finite? x) + "Neither infinite or NaN." + (or (infinite? x) (nan? x))) + +(declare negative-infinity ((Transfinite :a) (Num :a) => :a)) +(define negative-infinity + (negate infinity)) + +(define-instance (Transfinite Single-Float) + (define infinity + (lisp Single-Float () + float-features:single-float-positive-infinity)) + (define nan + (lisp Single-Float () + float-features:single-float-nan)) + (define (nan? x) + (Lisp Boolean (x) + #+(not allegro) + (float-features:float-NaN-p x) + #+allegro + (cl:and (float-features:float-NaN-p x) cl:t))) + (define (infinite? x) + (Lisp Boolean (x) + (float-features:float-infinity-p x)))) + +(define-instance (Transfinite Double-Float) + (define infinity + (lisp Double-Float () + float-features:double-float-positive-infinity)) + (define nan + (lisp Double-Float () + float-features:double-float-nan)) + (define (nan? x) + (Lisp Boolean (x) + #+(not allegro) + (float-features:float-NaN-p x) + #+allegro + (cl:and (float-features:float-NaN-p x) cl:t))) + (define (infinite? x) + (Lisp Boolean (x) + (float-features:float-infinity-p x)))) + +(declare negate (Num :a => :a -> :a)) +(define (negate x) + "The negation, or additive inverse, of `x`." + (- 0 x)) + +(declare abs ((Ord :a) (Num :a) => :a -> :a)) +(define (abs x) + "Absolute value of `x`." + (if (< x 0) + (negate x) + x)) + +(declare sign ((Ord :a) (Num :a) (Num :b) => :a -> :b)) +(define (sign x) + "The sign of `x`, where `(sign 0) = 1`." + (if (< x 0) + -1 + 1)) + +(declare ash (Integer -> Integer -> Integer)) +(define (ash x n) + "Compute the \"arithmetic shift\" of `x` by `n`. " + (lisp Integer (x n) (cl:ash x n))) + +(declare 1+ ((Num :num) => :num -> :num)) +(define (1+ num) + "Increment `num`." + (+ num 1)) + +(declare 1- ((Num :num) => :num -> :num)) +(define (1- num) + "Decrement `num`." + (- num 1)) + +(declare positive? ((Num :a) (Ord :a) => :a -> Boolean)) +(define (positive? x) + "Is `x` positive?" + (> x 0)) + +(declare negative? ((Num :a) (Ord :a) => :a -> Boolean)) +(define (negative? x) + "Is `x` negative?" + (< x 0)) + +(declare nonpositive? ((Num :a) (Ord :a) => :a -> Boolean)) +(define (nonpositive? x) + "Is `x` not positive?" + (<= x 0)) + +(declare nonnegative? ((Num :a) (Ord :a) => :a -> Boolean)) +(define (nonnegative? x) + "Is `x` not negative?" + (>= x 0)) + +(declare zero? (Num :a => :a -> Boolean)) +(define (zero? x) + "Is `x` zero?" + (== x 0)) + +(declare nonzero? (Num :a => :a -> Boolean)) +(define (nonzero? x) + "Is `x` not zero?" + (/= x 0)) diff --git a/library/math/bounded.coal b/library/math/bounded.coal new file mode 100644 index 000000000..ffbbd20fd --- /dev/null +++ b/library/math/bounded.coal @@ -0,0 +1,62 @@ +;;;; Numerical types with fixed bounds + +(package coalton-library/math/bounded + (import + coalton-library/builtin + coalton-library/classes + coalton-library/functions) + (export + Bounded + minBound + maxBound)) + +(define-class (Bounded :a) + "Types which have a maximum and minumum bound." + (minBound :a) + (maxBound :a)) + +(define-instance (Bounded U8) + (define minBound 0) ; 0 + (define maxBound 255)) ; 2^8-1 + +(define-instance (Bounded I8) + (define minBound -128) ; -1 * ceiling((2^8-1)/2) + (define maxBound 127)) ; ceiling((2^8-1)/2) + +(define-instance (Bounded U16) + (define minBound 0) ; 0 + (define maxBound 65535)) ; 2^16-1 + +(define-instance (Bounded I16) + (define minBound -32768) ; -1 * floor((2^16-1)/2) + (define maxBound 32767)) ; ceiling((2^16-1)/2) + +(define-instance (Bounded U32) + (define minBound 0) ; 0 + (define maxBound 4294967295)) ; 2^32-1 + +(define-instance (Bounded I32) + (define minBound -2147483648) ; -1 * ceiling((2^32-1)/2) + (define maxBound 2147483647)) ; floor((2^32-1)/2) + +(define-instance (Bounded U64) + (define minBound 0) ; 0 + (define maxBound 18446744073709551615)) ; 2^64-1 + +(define-instance (Bounded I64) + (define minBound -9223372036854775808) ; -1 * ceiling((2^64-1)/2) + (define maxBound 9223372036854775807)) ; floor((2^32-1)/2) + +(define-instance (Bounded IFix) + (define minBound + (lisp IFix () + cl:most-negative-fixnum)) + (define maxBound + (lisp IFix () + cl:most-positive-fixnum))) + +(define-instance (Bounded UFix) + (define minBound 0) + (define maxBound + (lisp UFix () + cl:most-positive-fixnum))) diff --git a/library/math/num.coal b/library/math/num.coal new file mode 100644 index 000000000..dbc5bbe40 --- /dev/null +++ b/library/math/num.coal @@ -0,0 +1,544 @@ +;;;; Instances for primitive numerical types + +(package coalton-library/math/num + (import + coalton-library/builtin + coalton-library/classes + coalton-library/functions + coalton-library/utils + coalton-library/math/arith + (float-features as ff) + (coalton-library/bits as bits)) + (import-from + coalton-library/hash + Hash)) + +;;; +;;; Constants +;;; + +(lisp-toplevel () + + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defconstant +fixnum-bits+ + #+sbcl sb-vm:n-fixnum-bits + #-sbcl (cl:1+ (cl:floor (cl:log cl:most-positive-fixnum 2)))) + (cl:defconstant +unsigned-fixnum-bits+ + (cl:1- +fixnum-bits+))) + +;;; +;;; Eq Instances +;;; + + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defmacro define-eq (type) + `(define-instance (Eq ,type) + (define (== a b) + (lisp Boolean (a b) + ;; Use cl:= so that (== 0.0 -0.0) => True + (cl:= a b))))))) + +(define-eq Integer) +(define-eq IFix) +(define-eq UFix) +(define-eq I8) +(define-eq U8) +(define-eq I16) +(define-eq U16) +(define-eq I32) +(define-eq U32) +(define-eq I64) +(define-eq U64) +(define-eq Single-Float) +(define-eq Double-Float) + +;;; +;;; Ord Instances +;;; + +(lisp-toplevel () + + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defmacro define-ord (type) + (cl:let ((>-spec (alexandria:format-symbol cl:*package* "~A->" type)) + (>=-spec (alexandria:format-symbol cl:*package* "~A->=" type)) + (<-spec (alexandria:format-symbol cl:*package* "~A-< type" type)) + (<=-spec (alexandria:format-symbol cl:*package* "~A-<=" type))) + + ;; Generates the instance and specializations to use more direct + ;; comparison functions when possible. + + `(progn + (define-instance (Ord ,type) + (define (<=> a b) + (lisp Ord (a b) + (cl:cond + ((cl:< a b) + LT) + ((cl:> a b) + GT) + (cl:t + EQ))))) + + (specialize > ,>-spec (,type -> ,type -> Boolean)) + (declare ,>-spec (,type -> ,type -> Boolean)) + (define (,>-spec a b) + (lisp Boolean (a b) + (to-boolean (cl:> a b)))) + + (specialize >= ,>=-spec (,type -> ,type -> Boolean)) + (declare ,>=-spec (,type -> ,type -> Boolean)) + (define (,>=-spec a b) + (lisp Boolean (a b) + (to-boolean (cl:>= a b)))) + + (specialize < ,<-spec (,type -> ,type -> Boolean)) + (declare ,<-spec (,type -> ,type -> Boolean)) + (define (,<-spec a b) + (lisp Boolean (a b) + (to-boolean (cl:< a b)))) + + (specialize <= ,<=-spec (,type -> ,type -> Boolean)) + (declare ,<=-spec (,type -> ,type -> Boolean)) + (define (,<=-spec a b) + (lisp Boolean (a b) + (to-boolean (cl:<= a b))))))))) + +(define-ord Integer) +(define-ord IFix) +(define-ord UFix) +(define-ord I8) +(define-ord U8) +(define-ord I16) +(define-ord U16) +(define-ord I32) +(define-ord U32) +(define-ord I64) +(define-ord U64) +(define-ord Single-Float) +(define-ord Double-Float) + +;;; +;;; Overflow checks for signed values +;;; + +(lisp-toplevel () + + (cl:declaim (cl:inline %unsigned->signed)) + (cl:defun %unsigned->signed (bits x) + ;; This is the two's complement conversion of X (interpreted as BITS + ;; bits) to a signed integer (as a Lisp object). + (cl:- + (cl:ldb (cl:byte (cl:1- bits) 0) x) + (cl:dpb 0 (cl:byte (cl:1- bits) 0) x))) + + (cl:defmacro %define-overflow-handler (name bits) + `(cl:progn + (cl:declaim (cl:inline ,name)) + (cl:defun ,name (value) + (cl:typecase value + ((cl:signed-byte ,bits) value) + (cl:otherwise + (cl:cerror "Continue, wrapping around." + ,(cl:format cl:nil "Signed value overflowed ~D bits." bits)) + (%unsigned->signed ,bits (cl:mod value ,(cl:expt 2 bits)))))))) + + + (%define-overflow-handler %handle-8bit-overflow 8) + (%define-overflow-handler %handle-16bit-overflow 16) + (%define-overflow-handler %handle-32bit-overflow 32) + (%define-overflow-handler %handle-64bit-overflow 64) + (%define-overflow-handler %handle-fixnum-overflow #.+fixnum-bits+) + +;;; +;;; Num instances for integers +;;; + + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defmacro define-num-checked (type overflow-handler) + "Define a `Num' instance for TYPE which signals on overflow." + `(define-instance (Num ,type) + (define (+ a b) + (lisp ,type (a b) + (,overflow-handler (cl:+ a b)))) + + (define (- a b) + (lisp ,type (a b) + (,overflow-handler (cl:- a b)))) + + (define (* a b) + (lisp ,type (a b) + (,overflow-handler (cl:* a b)))) + + (define (fromInt x) + (lisp ,type (x) + (,overflow-handler x)))))) + + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defmacro define-num-wrapping (type bits) + "Define a `Num' instance for TYPE which wraps on overflow." + `(define-instance (Num ,type) + (define (+ a b) + (lisp ,type (a b) + (cl:values (cl:mod (cl:+ a b) ,(cl:expt 2 bits))))) + + (define (- a b) + (lisp ,type (a b) + (cl:values (cl:mod (cl:- a b) ,(cl:expt 2 bits))))) + + (define (* a b) + (lisp ,type (a b) + (cl:values (cl:mod (cl:* a b) ,(cl:expt 2 bits))))) + + (define (fromInt x) + (lisp ,type (x) + (cl:values (cl:mod x ,(cl:expt 2 bits))))))))) + + +(define-num-checked Integer cl:identity) + +(define-num-checked I8 %handle-8bit-overflow) +(define-num-checked I16 %handle-16bit-overflow) +(define-num-checked I32 %handle-32bit-overflow) +(define-num-checked I64 %handle-64bit-overflow) +(define-num-checked IFix %handle-fixnum-overflow) + +(define-num-wrapping U8 8) +(define-num-wrapping U16 16) +(define-num-wrapping U32 32) +(define-num-wrapping U64 64) +(define-num-wrapping UFix #.+unsigned-fixnum-bits+) + +;;; +;;; Num instances for floats +;;; + +(lisp-toplevel () + + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defun %optional-coerce (z cl-type) + "Attempts to coerce Z to an Optional CL-TYPE, returns NONE if failed." + (cl:let ((x (cl:ignore-errors + (cl:coerce z cl-type)))) + (cl:if (cl:null x) + None + (Some x)))) + + (cl:defmacro define-num-float (type lisp-type) + "Define `Num' for TYPE" + + ;; + ;; CCL has a tendency to re-enable float traps. The explicit float + ;; trap masking keeps the test suite working during interactive + ;; development. + ;; + ;; Allegro appears to have some checks that make some arithmetic + ;; functions error on some inputs. The explicit checks in division + ;; keep the behavior consistent with IEEE 754. + ;; + + `(define-instance (Num ,type) + (define (+ a b) + (lisp ,type (a b) + (#+(not ccl) cl:progn + #+ccl ff:with-float-traps-masked #+ccl cl:t + (cl:+ a b)))) + + (define (- a b) + (lisp ,type (a b) + (#+(not ccl) cl:progn + #+ccl ff:with-float-traps-masked #+ccl cl:t + (cl:- a b)))) + + (define (* a b) + (lisp ,type (a b) + (#+(not ccl) cl:progn + #+ccl ff:with-float-traps-masked #+ccl cl:t + (cl:* a b)))) + + (define (fromInt x) + (match (lisp (Optional ,type) (x) + (%optional-coerce x ',lisp-type)) + ((Some x) x) + ((None) (if (< 0 x) + negative-infinity + infinity)))))))) + +(define-num-float Single-Float cl:single-float) +(define-num-float Double-Float cl:double-float) + +;;; +;;; Float to `Fraction' conversions +;;; + +(lisp-toplevel () + + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defmacro define-float-fraction-conversion (type) + `(define-instance (TryInto ,type Fraction String) + (define (tryInto x) + (if (finite? x) + (Ok (lisp Fraction (x) (cl:rational x))) + (Err "Could not convert NaN or infinity into a Fraction"))))))) + +(define-float-fraction-conversion Single-Float) +(define-float-fraction-conversion Double-Float) + +;;; +;;; `Dividable' and `Reciprocable' instances for floata +;;; + +(lisp-toplevel () + + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defmacro define-reciprocable-float (type) + `(define-instance (Reciprocable ,type) + (define (/ x y) + (cond + #+allegro + ((or (nan? x) + (nan? y)) + nan) + + #+allegro + ((and (== x 0) (== y 0)) + nan) + + #+allegro + ((and (positive? x) (== y 0)) + infinity) + + #+allegro + ((and (negative? x) (== y 0)) + negative-infinity) + + (True + (lisp ,type (x y) + (#+(not ccl) cl:progn + #+ccl ff:with-float-traps-masked #+ccl cl:t + (cl:/ x y)))))) + + (define (reciprocal x) + (cond + #+allegro + ((== x 0) + infinity) + + (True + (lisp ,type (x) + (#+(not ccl) cl:progn + #+ccl ff:with-float-traps-masked #+ccl cl:t + (cl:/ x))))))))) + + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defmacro define-dividable-float (type lisp-type) + `(define-instance (Dividable Integer ,type) + (define (general/ x y) + (if (== y 0) + (/ (fromInt x) (fromInt y)) + (match (lisp (Optional ,type) (x y) + (%optional-coerce (cl:/ x y) ',lisp-type)) + ((Some x) x) + ((None) (if (and (> x 0) (> y 0)) + infinity + negative-infinity))))))))) + +(define-reciprocable-float Single-Float) +(define-reciprocable-float Double-Float) + +(define-dividable-float Single-Float cl:single-float) +(define-dividable-float Double-Float cl:double-float) + +;;; +;;; `Bits' instances +;;; + +(lisp-toplevel () + + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defmacro define-bits-checked (type handle-overflow) + `(define-instance (bits:Bits ,type) + (define (bits:and a b) + (lisp ,type (a b) + (cl:logand a b))) + + (define (bits:or a b) + (lisp ,type (a b) + (cl:logior a b))) + + (define (bits:xor a b) + (lisp ,type (a b) + (cl:logxor a b))) + + (define (bits:not x) + (lisp ,type (x) + (cl:lognot x))) + + (define (bits:shift amount bits) + (lisp ,type (amount bits) + (,handle-overflow (cl:ash bits amount))))))) + + (cl:declaim (cl:inline unsigned-lognot)) + (cl:defun unsigned-lognot (int n-bits) + (cl:declare (cl:type cl:unsigned-byte int) + (cl:type cl:unsigned-byte n-bits) + (cl:values cl:unsigned-byte)) + + (cl:- (cl:ash 1 n-bits) int 1)) + + (cl:declaim (cl:inline handle-unsigned-overflow)) + (cl:defun handle-unsigned-overflow (int n-bits) + (cl:declare (cl:type cl:unsigned-byte int) + (cl:type cl:unsigned-byte n-bits) + (cl:values cl:unsigned-byte)) + + (cl:logand (cl:1- (cl:ash 1 n-bits)) int)) + + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defmacro define-bits-wrapping (type width) + `(define-instance (bits:Bits ,type) + (define (bits:and a b) + (lisp ,type (a b) + (cl:logand a b))) + + (define (bits:or a b) + (lisp ,type (a b) + (cl:logior a b))) + + (define (bits:xor a b) + (lisp ,type (a b) + (cl:logxor a b))) + + (define (bits:not x) + (lisp ,type (x) + (unsigned-lognot x ,width))) + + (define (bits:shift amount bits) + (lisp ,type (amount bits) + (cl:logand (cl:ash bits amount) + ,(cl:1- (cl:ash 1 width))))))))) + +(define-bits-checked Integer cl:identity) + +(define-bits-checked I8 %handle-8bit-overflow) +(define-bits-checked I16 %handle-16bit-overflow) +(define-bits-checked I32 %handle-32bit-overflow) +(define-bits-checked I64 %handle-64bit-overflow) +(define-bits-checked IFix %handle-fixnum-overflow) + +(define-bits-wrapping U8 8) +(define-bits-wrapping U16 16) +(define-bits-wrapping U32 32) +(define-bits-wrapping U64 64) +(define-bits-wrapping UFix #.+unsigned-fixnum-bits+) + + + ;; `Hash' instances + +(define-instance (Hash Integer) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash I8) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash I16) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash I32) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash I64) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash U8) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash U16) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash U32) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash U64) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash IFix) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash UFix) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash Single-Float) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + +(define-instance (Hash Double-Float) + (define (hash item) + (lisp Hash (item) + (cl:sxhash item)))) + + ;; + ;; Default instances + ;; + +(define-instance (Default I8) + (define (default) 0)) + +(define-instance (Default U8) + (define (default) 0)) + +(define-instance (Default I16) + (define (default) 0)) + +(define-instance (Default I32) + (define (default) 0)) + +(define-instance (Default I64) + (define (default) 0)) + +(define-instance (Default U16) + (define (default) 0)) + +(define-instance (Default U32) + (define (default) 0)) + +(define-instance (Default U64) + (define (default) 0)) + +(define-instance (Default IFix) + (define (default) 0)) + +(define-instance (Default UFix) + (define (default) 0)) + +(define-instance (Default Integer) + (define (default) 0)) + +(define-instance (Default Double-Float) + (define (default) 0)) + +(define-instance (Default Single-Float) + (define (default) 0)) diff --git a/library/primitive-types.coal b/library/primitive-types.coal new file mode 100644 index 000000000..7d8355900 --- /dev/null +++ b/library/primitive-types.coal @@ -0,0 +1,68 @@ +;;;; Primitive types + +(package coalton) + +(repr :native cl:t) +(define-type Void) + +;; Boolean is an early type +(declare True Boolean) +(define True (lisp Boolean () cl:t)) + +(declare False Boolean) +(define False (lisp Boolean () cl:nil)) + +;; Unit is an early type +(declare Unit Unit) +(define Unit (lisp Unit () 'coalton::Unit/Unit)) + +;; List is an early type +(declare Cons (:a -> (List :a) -> (List :a))) +(define (Cons x xs) + (lisp (List :a) (x xs) + (cl:cons x xs))) + +(declare Nil (List :a)) +(define Nil + (lisp (List :a) () + cl:nil)) + +(repr :native (cl:unsigned-byte 8)) +(define-type U8 + "Unsigned 8-bit integer capable of storing values in `[0, 255]`. Uses `(unsigned-byte 8)`.") + +(repr :native (cl:unsigned-byte 16)) +(define-type U16 + "Unsigned 16-bit integer capable of storing values in `[0, 65535]`. Uses `(unsigned-byte 16)`.") + +(repr :native (cl:unsigned-byte 32)) +(define-type U32 + "Unsigned 32-bit integer capable of storing values in `[0, 4294967295]`. Uses `(unsigned-byte 32)`.") + +(repr :native (cl:unsigned-byte 64)) +(define-type U64 + "Unsigned 64-bit integer capable of storing values in `[0, 18446744073709551615]`. Uses `(unsigned-byte 64)`.") + +(repr :native (cl:signed-byte 8)) +(define-type I8 + "Signed 8-bit integer capable of storing values in `[-128, 127]`. Uses `(signed-byte 8)`.") + +(repr :native (cl:signed-byte 16)) +(define-type I16 + "Signed 16-bit integer capable of storing values in `[-32768, 32767]`. Uses `(signed-byte 16)`.") + +(repr :native (cl:signed-byte 32)) +(define-type I32 + "Signed 32-bit integer capable of storing values in `[-2147483648, 2147483647]`. Uses `(signed-byte 32)`.") + +(repr :native (cl:signed-byte 64)) +(define-type I64 + "Signed 64-bit integer capable of storing values in `[-9223372036854775808, 9223372036854775807]`. Uses `(signed-byte 64)`.") + +(repr :native cl:fixnum) +(define-type IFix + "Non-allocating tagged integer; range is platform-dependent. Does not error on overflow. Uses `fixnum`.") + +(repr :native (cl:and cl:fixnum cl:unsigned-byte)) +(define-type UFix + "Non-allocating tagged non-negative integer; range is platform-dependent. Uses `(and fixnum unsigned-byte)`.") diff --git a/library/types.coal b/library/types.coal new file mode 100644 index 000000000..1629f0468 --- /dev/null +++ b/library/types.coal @@ -0,0 +1,95 @@ +(package coalton-library/types + (export + Proxy + proxy-of + as-proxy-of + proxy-inner + LispType + RuntimeRepr + runtime-repr + runtime-repr-of)) + +(repr :enum) +(define-type (Proxy :a) + "Proxy holds no data, but has a phantom type parameter." + Proxy) + +(declare proxy-of (:a -> Proxy :a)) +(define (proxy-of _) + "Returns a Proxy containing the type of the parameter." + Proxy) + +(declare as-proxy-of (:a -> Proxy :a -> :a)) +(define (as-proxy-of x _) + "Returns the parameter, forcing the proxy to have the same type as the parameter." + x) + +(declare proxy-inner (Proxy (:a :b) -> Proxy :b)) +(define (proxy-inner _) + Proxy) + +(repr :native (cl:or cl:symbol cl:list)) +(define-type LispType + "The runtime representation of a Coalton type as a lisp type.") + +(define-class (RuntimeRepr :a) + "Types which have a runtime LispType representation. + +`runtime-repr` corresponds to the type emitted by the Coalton compiler for the type parameter to the given Proxy. + +The compiler will auto-generate instances of `RuntimeRepr` for all defined types." + (runtime-repr (Proxy :a -> LispType))) + +(declare runtime-repr-of (RuntimeRepr :a => :a -> LispType)) +(define (runtime-repr-of x) + "Returns the runtime representation of the type of the given value." + (runtime-repr (proxy-of x))) + + ;; Additional RuntimeRepr instances for early-defined types + +(define-instance (RuntimeRepr Boolean) + (define (runtime-repr _) + (lisp LispType () 'cl:boolean))) + +(define-instance (RuntimeRepr Char) + (define (runtime-repr _) + (lisp LispType () 'cl:character))) + +(define-instance (RuntimeRepr Integer) + (define (runtime-repr _) + (lisp LispType () 'cl:integer))) + +(define-instance (RuntimeRepr Single-Float) + (define (runtime-repr _) + (lisp LispType () 'cl:single-float))) + +(define-instance (RuntimeRepr Double-Float) + (define (runtime-repr _) + (lisp LispType () 'cl:double-float))) + +(define-instance (RuntimeRepr String) + (define (runtime-repr _) + (lisp LispType () 'cl:string))) + +(define-instance (RuntimeRepr Fraction) + (define (runtime-repr _) + (lisp LispType () 'cl:rational))) + +(define-instance (RuntimeRepr (:a -> :b)) + (define (runtime-repr _) + (lisp LispType () 'coalton-impl/runtime/function-entry:function-entry))) + +(define-instance (RuntimeRepr (List :a)) + (define (runtime-repr _) + (lisp LispType () 'cl:list))) + + ;; The compiler will not auto-generate RuntimeRepr instances for + ;; types defined in this file to avoid circular dependencies. + +(define-instance (RuntimeRepr LispType) + (define (runtime-repr _) + (lisp LispType () '(cl:or cl:symbol cl:list)))) + +(define-instance (RuntimeRepr (Proxy :a)) + (define (runtime-repr _) + (lisp LispType () '(cl:member 'proxy/proxy)))) diff --git a/library/vector.coal b/library/vector.coal new file mode 100644 index 000000000..58412c3a1 --- /dev/null +++ b/library/vector.coal @@ -0,0 +1,358 @@ +(package coalton-library/vector + (import + coalton-library/builtin + coalton-library/functions + coalton-library/classes + (coalton-library/types as types) + (coalton-library/list as list) + (coalton-library/cell as cell) + (coalton-library/iterator as iter) + (coalton-library/randomaccess as ram)) + (export + Vector + new + with-capacity + with-initial-element + singleton + length + capacity + empty? + singleton? + copy + set-capacity! + clear! + push! + pop! + pop-unsafe! + index + index-unsafe + set! + head + head-unsafe + last + last-unsafe + extend! + find-elem + append + swap-remove! + swap-remove-unsafe! + sort! + sort-by! + make)) + +;; +;; Vector +;; + +(repr :native (cl:and (cl:vector cl:t) (cl:not cl:simple-vector))) +(define-type (Vector :a)) + +(declare new (Unit -> Vector :a)) +(define (new _) + "Create a new empty vector" + (with-capacity 0)) + +(declare with-capacity (UFix -> Vector :a)) +(define (with-capacity n) + "Create a new vector with `n` elements preallocated." + (lisp (Vector :a) (n) + (cl:make-array n :fill-pointer 0 :adjustable cl:t :element-type cl:t))) + +(declare with-initial-element (UFix -> :a -> Vector :a)) +(define (with-initial-element n x) + "Create a new vector with `n` elements equal to `x`." + (let v = (with-capacity n)) + (extend! v (iter:repeat-for x n)) + v) + +(declare singleton (:a -> Vector :a)) +(define (singleton x) + "Create a new vector with a single element equal to `x`" + (with-initial-element 1 x)) + +(declare length (Vector :a -> UFix)) +(define (length v) + "Returns the length of `v`." + (lisp UFix (v) + (cl:length v))) + +(declare capacity (Vector :a -> UFix)) +(define (capacity v) + "Returns the number of elements that `v` can store without resizing." + (lisp UFix (v) + (cl:array-dimension v 0))) + +(declare empty? (Vector :a -> Boolean)) +(define (empty? v) + "Is `v` empty?" + (== 0 (length v))) + +(declare singleton? (Vector :a -> Boolean)) +(define (singleton? v) + "Is `v` a singleton?" + (== 1 (length v))) + +(declare copy (Vector :a -> Vector :a)) +(define (copy v) + "Return a new vector containing the same elements as `v`." + (lisp (Vector :a) (v) + ;; We use COPY-ARRAY and not COPY-SEQ to get identical + ;; adjustable properties. + (alexandria:copy-array v))) + +(declare set-capacity! (UFix -> Vector :a -> Unit)) +(define (set-capacity! new-capacity v) + "Set the capacity of `v` to `new-capacity`. Setting the capacity to lower then the length will remove elements from the end." + (let shrinking = (< new-capacity (length v))) + (lisp Unit (v shrinking new-capacity) + ;; If the array is getting larger then don't change the + ;; fill pointer + (cl:adjust-array v new-capacity :fill-pointer shrinking) + Unit)) + +(declare clear! (Vector :a -> Unit)) +(define (clear! v) + "Set the capacity of `v` to `0`." + (set-capacity! 0 v)) + +(declare push! (:a -> Vector :a -> UFix)) +(define (push! item v) + "Append `item` to `v` and resize `v` if necessary, returning the index of the new item." + (lisp UFix (item v) + (cl:vector-push-extend item v))) + +(declare pop! (Vector :a -> Optional :a)) +(define (pop! v) + "Remove and return the last item of `v`." + (if (empty? v) + None + (Some (pop-unsafe! v)))) + +(declare pop-unsafe! (Vector :a -> :a)) +(define (pop-unsafe! v) + "Remove and return the last item of `v` without checking if the vector is empty." + (lisp :a (v) + (cl:vector-pop v))) + +(declare index (UFix -> Vector :a -> Optional :a)) +(define (index index v) + "Return the `index`th element of `v`." + (if (>= index (length v)) + None + (Some (index-unsafe index v)))) + +(declare index-unsafe (UFix -> Vector :a -> :a)) +(define (index-unsafe idx v) + "Return the `idx`th element of `v` without checking if the element exists." + (lisp :a (idx v) + (cl:aref v idx))) + +(declare set! (UFix -> :a -> Vector :a -> Unit)) +(define (set! idx item v) + "Set the `idx`th element of `v` to `item`. This function left intentionally unsafe because it does not have a return value to check." + (lisp Void (idx item v) + (cl:setf (cl:aref v idx) item)) + Unit) + +(declare head (Vector :a -> Optional :a)) +(define (head v) + "Return the first item of `v`." + (index 0 v)) + +(declare head-unsafe (Vector :a -> :a)) +(define (head-unsafe v) + "Return the first item of `v` without first checking if `v` is empty." + (index-unsafe 0 v)) + +(declare last (Vector :a -> Optional :a)) +(define (last v) + "Return the last element of `v`." + (index (- (length v) 1) v)) + +(declare last-unsafe (Vector :a -> :a)) +(define (last-unsafe v) + "Return the last element of `v` without first checking if `v` is empty." + (index-unsafe (- (length v) 1) v)) + +(declare find-elem (Eq :a => :a -> Vector :a -> Optional UFix)) +(define (find-elem e v) + "Find the index of element `e` in `v`." + (let ((test (fn (elem) + (== elem e)))) + + (lisp (Optional UFix) (v test) + (cl:let ((pos (cl:position-if + (cl:lambda (x) + (cl:eq cl:t (call-coalton-function test x))) + v))) + (cl:if pos + (Some pos) + None))))) + +(declare append (Vector :a -> Vector :a -> Vector :a)) +(define (append v1 v2) + "Create a new vector containing the elements of `v1` followed by the elements of `v2`." + (let out = (with-capacity (+ (length v1) (length v2)))) + (extend! out v1) + (extend! out v2) + out) + +(declare swap-remove! (UFix -> Vector :a -> Optional :a)) +(define (swap-remove! idx vec) + "Remove the element `idx` from `vec` and replace it with the last element in `vec`. Then return the removed element." + (if (>= idx (length vec)) + None + (Some (swap-remove-unsafe! idx vec)))) + +(declare swap-remove-unsafe! (UFix -> Vector :a -> :a)) +(define (swap-remove-unsafe! idx vec) + "Remove the element `idx` from `vec` and replace it with the last element in `vec` without bounds checking. Then return the removed element." + (if (== (+ 1 idx) (length vec)) + (pop-unsafe! vec) + (progn + (let out = (index-unsafe idx vec)) + (set! idx (pop-unsafe! vec) vec) + out))) + +(declare sort-by! ((:a -> :a -> Boolean) -> Vector :a -> Unit)) +(define (sort-by! f v) + "Sort a vector in-place with predicate function `f`." + (lisp Void (v f) + (cl:sort + v + (cl:lambda (a b) + (call-coalton-function f a b)))) + Unit) + +(declare sort! (Ord :a => Vector :a -> Unit)) +(define (sort! v) + "Sort a vector in-place in ascending order." + (sort-by! < v)) + +(declare extend! (iter:IntoIterator :container :elt => Vector :elt -> :container -> Unit)) +(define (extend! vec iter) + "Push every element in `iter` to the end of `vec`." + (let iter = (iter:into-iter iter)) + + ;; If the iterator is known to require more capacity then vec has, + ;; resize before pushing elements + (let size = (with-default 0 (iter:size-hint iter))) + (let remaining-capacity = (- (capacity vec) (length vec))) + (when (> size remaining-capacity) + (set-capacity! (- size remaining-capacity) vec)) + + (iter:for-each! + (fn (x) + (push! x vec) + Unit) + iter) + Unit) + + ;; + ;; Instances + ;; + +(define-instance (Eq :a => Eq (Vector :a)) + (define (== v1 v2) + (if (/= (length v1) (length v2)) + False + (iter:every! id (iter:zip-with! == (iter:into-iter v1) (iter:into-iter v2)))))) + +(define-instance (Semigroup (Vector :a)) + (define <> append)) + +(define-instance (Functor Vector) + (define (map f v) + (let out = (with-capacity (length v))) + (iter:for-each! + (fn (x) + (push! (f x) out) + Unit) + (iter:into-iter v)) + out)) + +(define-instance (Foldable Vector) + (define (fold f init vec) + (lisp :a (f init vec) + (cl:reduce + (cl:lambda (b a) + (call-coalton-function f b a)) + vec + :initial-value init))) + (define (foldr f init vec) + (lisp :a (f init vec) + (cl:reduce + (cl:lambda (a b) + (call-coalton-function f a b)) + vec + :initial-value init + :from-end cl:t)))) + +(define-instance (ram:RandomAccess (Vector :t) :t) + (define (ram:make n x) + (with-initial-element n x)) + (define (ram:length a) + (length a)) + (define (ram:readable? _) + True) + (define (ram:writable? _) + True) + (define (ram:unsafe-aref a n) + (index-unsafe n a)) + (define (ram:unsafe-set! a n x) + (set! n x a))) + +(define-instance (Into (List :a) (Vector :a)) + (define (into lst) + (let ((out (with-capacity (list:length lst))) + (inner + (fn (lst) + (match lst + ((Cons x xs) + (progn + (push! x out) + (inner xs))) + ((Nil) Unit))))) + (progn + (inner lst) + out)))) + +(define-instance (Into (Vector :a) (List :a)) + (define (into v) + (iter:collect! (iter:into-iter v)))) + +(define-instance (Iso (Vector :a) (List :a))) + +(define-instance (iter:IntoIterator (Vector :a) :a) + (define (iter:into-iter vec) + (let idx = (cell:new 0)) + (iter:with-size + (fn () + (let res = (index (cell:read idx) vec)) + (cell:increment! idx) + res) + (length vec)))) + +(define-instance (iter:FromIterator (Vector :a) :a) + (define (iter:collect! iter) + (let size = (with-default 0 (iter:size-hint iter))) + (let vec = (with-capacity size)) + (iter:for-each! (fn (x) + (push! x vec) + Unit) + iter) + vec)) + +(define-instance (Default (Vector :a)) + (define default new)) + +(lisp-toplevel () + (cl:defmacro make (cl:&rest elements) + "Construct a `Vector' containing the ELEMENTS, in the order listed." + (cl:let* ((length (cl:length elements)) + (vec (cl:gensym "VEC-"))) + `(progn + (let ,vec = (with-capacity ,length)) + ,@(cl:loop :for elt :in elements + :collect `(push! ,elt ,vec)) + ,vec))))