This is an experiment on trying to implement record syntax for Coalton. The Haskell equivalent.
The define-record
definition:
(cl:defmacro define-record (name members)
(cl:flet ((make-member-matcher (member)
(cl:loop :for m :in members
:collect (cl:if (cl:eq (cl:first m) member)
member
'coalton:_))))
`(cl:progn
;; Type definition
(coalton-toplevel
(coalton:define-type ,name
(,name ,@(cl:mapcar 'cl:second members))))
;; Accessors
,@(cl:loop :for member :in members
:collect `(coalton-toplevel
(coalton:define (,(cl:first member) instance)
(coalton:match instance
((,name ,@(make-member-matcher (cl:first member)))
,(cl:first member))))))
;; Constructor
(cl:defmacro ,(cl:intern (cl:concatenate 'cl:string "MAKE-" (cl:symbol-name name))) (cl:&rest args)
`(,',name ,,@(cl:loop :for member :in members
:collect `(cl:or (cl:getf args ',(cl:first member))
(cl:error "Missing record argument: ~s" ',(cl:first member))))))
;; Updater
(cl:defmacro ,(cl:intern (cl:concatenate 'cl:string "UPDATE-" (cl:symbol-name name))) (instance cl:&rest args)
`(,',name ,,@(cl:loop :for member :in members
:collect `(cl:or (cl:getf args ',(cl:first member))
`(,',(cl:first member) ,instance)))))
)))
Then we can define Coalton types using named arguments:
(define-record Person
((firstname String)
(lastname String)
(age Integer)))
Accessor functions are generated:
firstname :: Person -> String
lastname :: Person -> String
age :: Person -> Integer
(coalton (firstname (make-person
age 38
firstname "Mariano"
lastname "Montone")))
=> "Mariano"
And a make-person
constructor function that uses named parameters:
(coalton (make-person
firstname "Mariano"
age 38
lastname "Montone"))
=>
#.(PERSON "Mariano" "Montone" 38)
Arguments can be given in any order:
(coalton (make-person
age 38
lastname "Montone"
firstname "Mariano"))
=>
#.(PERSON "Mariano" "Montone" 38)
There's also an "updater" function generated, that constructs a new instance from another, and modifies only the members passed as arguments. Very handy for updating individual members of large records.
(coalton
(progn
(let p = (make-person firstname "Mariano"
lastname "Montone"
age 33))
(let newp = (update-person p age 20))
newp))
=> #.(PERSON "Mariano" "Montone" 20)
This is the macro expansion:
(COMMON-LISP:PROGN
(COALTON-TOPLEVEL
(DEFINE-TYPE PERSON
(PERSON STRING STRING INTEGER)))
(COALTON-TOPLEVEL
(DEFINE (FIRSTNAME INSTANCE)
(MATCH INSTANCE
((PERSON FIRSTNAME _ _) FIRSTNAME))))
(COALTON-TOPLEVEL
(DEFINE (LASTNAME INSTANCE)
(MATCH INSTANCE
((PERSON _ LASTNAME _) LASTNAME))))
(COALTON-TOPLEVEL
(DEFINE (AGE INSTANCE)
(MATCH INSTANCE
((PERSON _ _ AGE) AGE))))
(COMMON-LISP:DEFMACRO MAKE-PERSON (COMMON-LISP:&REST ARGS)
`(,'PERSON
,(COMMON-LISP:OR (COMMON-LISP:GETF ARGS 'FIRSTNAME)
(COMMON-LISP:ERROR "Missing record argument: ~s"
'FIRSTNAME))
,(COMMON-LISP:OR (COMMON-LISP:GETF ARGS 'LASTNAME)
(COMMON-LISP:ERROR "Missing record argument: ~s"
'LASTNAME))
,(COMMON-LISP:OR (COMMON-LISP:GETF ARGS 'AGE)
(COMMON-LISP:ERROR "Missing record argument: ~s" 'AGE))))
(COMMON-LISP:DEFMACRO UPDATE-PERSON (INSTANCE COMMON-LISP:&REST ARGS)
`(,'PERSON
,(COMMON-LISP:OR (COMMON-LISP:GETF ARGS 'FIRSTNAME)
`(,'FIRSTNAME ,INSTANCE))
,(COMMON-LISP:OR (COMMON-LISP:GETF ARGS 'LASTNAME)
`(,'LASTNAME ,INSTANCE))
,(COMMON-LISP:OR (COMMON-LISP:GETF ARGS 'AGE) `(,'AGE ,INSTANCE)))))
PROBLEM:
Top-level macros are not supported in Coalton, so this implementation is not enough for usage yet, as define-record
cannot used inside coalton-toplevel
.