Skip to content

Instantly share code, notes, and snippets.

@mmontone
Last active December 19, 2021 04:42
Show Gist options
  • Save mmontone/990f5731f5dd54973f327c9ceecf3386 to your computer and use it in GitHub Desktop.
Save mmontone/990f5731f5dd54973f327c9ceecf3386 to your computer and use it in GitHub Desktop.
Record syntax experiment for Coalton

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment