Skip to content

define/curry - Defines an automatically currying procedure #5

Description

@agj

Macro

Defines an automatically currying procedure in a single step. Uses curry internally.

(begin-for-syntax
  (define-syntax-class name-params
    #:description "name and parameters clause"
    (pattern (name:id params:id ...+)
             #:fail-when (check-duplicate-identifier
                          (syntax->list #'(params ...)))
             "duplicate parameter name")))

(define-syntax (define/curry stx)
  (syntax-parse stx
    [(_ np:name-params body ...+)
     #`(define np.name
         (curry #,(syntax/loc stx
                    (λ (np.params ...)
                      body ...))))]))

Example

;; Define a procedure like this:

(define/curry (insert-between mid left right)
  (string-join (list left mid right) ""))

;; To use it like this:

(define dash-between (insert-between "-"))

(dash-between "left" "right") ; "left-right"

;; Or currying wherever necessary:

(((insert-between "-") "left") "right") ; "left-right"

Before and After

This code-cleaning macro reduces some boilerplate necessary to define a curried function. Code like this:

(define insert-between
  (curry
   (λ (mid left right)
     (string-join (list left mid right) ""))))

Is simplified to this, identical in form to defining a regular procedure:

(define/curry (insert-between mid left right)
  (string-join (list left mid right) ""))

I wrote an earlier version of this macro when implementing some Haskell code in Racket, and thought it was useful enough to share!

Licence

I release the above code under the MIT license, and accompanying text under the Creative Commons Attribution 4.0 International license.

Activity

  1. agj commented on Jul 11, 2021

    @agj
    Author

    I'm not very experienced writing Racket or macros, so any improvement suggestions are welcome. 😊

  2. changed the title [-]define/curry[/-] [+]`define/curry` - Defines an automatically currying procedure[/+] on Jul 11, 2021
  3. Fictitious-Rotor commented on Jul 12, 2021

    @Fictitious-Rotor

    I'm not very experienced writing Racket or macros, so any improvement suggestions are welcome. 😊

    This is a great little macro! Did you know, however, that there's another form for define that can achieve this?

    (define ((insert-between2 mid) left right)
        (string-join (list left mid right) ""))

    The docs show that the head form in the grammar is recursive :)

  4. agj commented on Jul 12, 2021

    @agj
    Author

    Hi, Rotor! Thanks! Yes, I'm aware of that syntax. However as far as I understand it, that syntax doesn't allow you to do something like this. All of the following are equivalent:

    (insert-between "-" "left" "right")
    ((insert-between "-" "left") "right")
    ((insert-between "-") "left" "right")
    ((insert-between) "-" "left" "right")
  5. Fictitious-Rotor commented on Jul 12, 2021

    @Fictitious-Rotor

    Hi, Rotor! Thanks! Yes, I'm aware of that syntax. However as far as I understand it, that syntax doesn't allow you to do something like this. All of the following are equivalent:

    (insert-between "-" "left" "right")
    ((insert-between "-" "left") "right")
    ((insert-between "-") "left" "right")
    ((insert-between) "-" "left" "right")

    Oh wow you're totally right!
    Perhaps you should add some of those examples to your original post!

  6. agj commented on Jul 13, 2021

    @agj
    Author

    @Fictitious-Rotor I updated with a link to the curry function's docs so that it doesn't look like my macro is doing any magic, and extended the example code a tiny bit. 👍

  7. bennn commented on Jul 15, 2021

    @bennn
    Member

    Looks great!

    Small suggestion: move the syntax class to the toplevel. It'll have to go inside a (begin-for-syntax ....) and you will probably need to add for-syntax requires.

    Annoying plumbing suggestion: use syntax/loc or quasisyntax/loc so that errors for things like (insert-between 1 2 3 4) point to the define/curry line instead of pointing to the lambda in the macro body.

  8. agj commented on Jul 16, 2021

    @agj
    Author

    Hi, @bennn! Thanks! I tried your suggestions. As for syntax/loc (which I didn't know about before, so that was cool to learn), I didn't notice any change adding it, and without it it seems to behave well, so I guess this is a case in which it's not needed? Like this:

    (require (for-syntax syntax/parse))
    
    (begin-for-syntax
      (define-syntax-class name-params
        #:description "name and parameters clause"
        (pattern (name:id params:id ...+)
                 #:fail-when (check-duplicate-identifier
                              (syntax->list #'(params ...)))
                 "duplicate parameter name")))
    
    (define-syntax (define/curry stx)
      (syntax-parse stx
        [(_ np:name-params body ...+)
         #'(define np.name
             (curry (λ (np.params ...)
                      body ...)))]))
    
    (define/curry (insert-between mid left right)
      (string-join (list left mid right) ""))
    
    (insert-between 2 3 3 4 4 44) ; This line gets highlighted with an error
  9. bennn commented on Jul 16, 2021

    @bennn
    Member

    Oh, that's great that the line with the function call gets highlighted.

    When I run on the command line, though, it prints the function and says that function is defined on line 17 (inside the define-syntax)

    curried:...test.rkt:17:16: arity mismatch;
     the expected number of arguments does not match the given number
    

    Maybe a better way to phrase it is: get (displayln insert-between) and (displayln (object-name insert-between)) to print the right line.

    If the definition of insert-between is in a different module than the macro, I think the name will be even more confusing.

  10. agj commented on Jul 17, 2021

    @agj
    Author

    Yeah you're right… I tried using syntax/loc but the behavior was the same. Maybe I'm just not using it right though. I tried surrounding both the define and the curry parts. I'll try again later using those functions, as it does seem like a useful thing in general to know for API ergonomics.

  11. bennn commented on Jul 22, 2021

    @bennn
    Member

    Hint: the λ is what needs a loc

  12. agj commented on Aug 20, 2021

    @agj
    Author

    @bennn Sorry for taking so long! I got a bit busy and distracted, but I tried your hint just now and it worked great! Thanks for the tip.

  13. added a commit that references this issue on Sep 30, 2021
  14. added 2 commits that reference this issue on Oct 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions