Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

define/curry - Defines an automatically currying procedure #5

Open
agj opened this issue Jul 11, 2021 · 11 comments
Open

define/curry - Defines an automatically currying procedure #5

agj opened this issue Jul 11, 2021 · 11 comments

Comments

@agj
Copy link

agj commented Jul 11, 2021

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.

@agj
Copy link
Author

agj commented Jul 11, 2021

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

@agj agj changed the title define/curry define/curry - Defines an automatically currying procedure Jul 11, 2021
@Fictitious-Rotor
Copy link

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 :)

@agj
Copy link
Author

agj commented Jul 12, 2021

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")

@Fictitious-Rotor
Copy link

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!

@agj
Copy link
Author

agj commented Jul 13, 2021

@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. 👍

@bennn
Copy link
Member

bennn commented Jul 15, 2021

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.

@agj
Copy link
Author

agj commented Jul 16, 2021

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

@bennn
Copy link
Member

bennn commented Jul 16, 2021

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.

@agj
Copy link
Author

agj commented Jul 17, 2021

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.

@bennn
Copy link
Member

bennn commented Jul 22, 2021

Hint: the λ is what needs a loc

@agj
Copy link
Author

agj commented Aug 20, 2021

@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.

bennn added a commit to syntax-objects/syntax-parse-example that referenced this issue Sep 30, 2021
bennn pushed a commit to syntax-objects/syntax-parse-example that referenced this issue Oct 27, 2021
bennn pushed a commit to syntax-objects/syntax-parse-example that referenced this issue Oct 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants