Using built-in math operators with custom struct
Asked Answered
A

1

5

I want to be able to do something like this:

(struct point (x y))

(define p1 (point 1 2))
(define p2 (point 10 20))

(+ p1 p2)  ; -> (point 11 22)

Is it possible to teach a struct like point to work with built-in math operators like +?

The docs seem to manage to implement custom (equal? ...) handling in section 5.5 on this page. What I'm trying to do is quite similar ...

Or should I just define function like (point-add p1 p2)?

Arletha answered 27/4, 2019 at 1:11 Comment(0)
D
7

You can either

  1. Go with point-add
  2. Use your own + that matches against all possible value types that you want to take on. This is sufficient if you know all possible value types beforehand, but it wouldn't be easy to extend it to include newly created struct definitions in client's code. For example:

    ;; We will "shadow" Racket's + with our own +, but we still
    ;; need the functionality of Racket's +, so let's require
    ;; Racket's + but use the name racket:+ instead
    (require (only-in racket/base [+ racket:+]))
    
    (struct point (x y) #:transparent)
    
    (define (+ x y)
      (match* (x y)
        [((point a b) (point c d)) (point (+ a c) (+ b d))]
        [((point _ _) _) (error '+ "Can't add a point with non point")]
        [(_ (point _ _)) (error '+ "Can't add a point with non point")]
        [(_ _) (racket:+ x y)]))
    
    ;; in client's code
    
    (+ (point 1 2) (point 3 4)) ;=> (point 4 6)
    (+ 1 2)                     ;=> 3
    
  3. Define a new generics so that we can do something similar to gen:equal+hash for equal?. For example:

    (require racket/generic
             (only-in racket/base [+ racket:+]))
    
    (define-generics addable
      (add addable _)
      #:fast-defaults ([number?
                        (define (add x y) (racket:+ x y))]))
    
    (define + add)
    
    ;; in client's code
    
    (struct point (x y)
      #:transparent
      #:methods gen:addable
      [(define (add x y)
         (match* (x y)
           [((point a b) (point c d)) (point (+ a c) (+ b d))]
           [(_ _) (error 'add "Can't add a point with non point")]))])
    
    (struct point-3d (x y z)
      #:transparent
      #:methods gen:addable
      [(define (add x y)
         (match* (x y)
           [((point-3d a b c) (point-3d d e f))
            (point-3d (+ a d) (+ b e) (+ c f))]
           [(_ _) (error '+ "Can't add a point-3d with non point-3d")]))])
    
    (+ (point 1 2) (point 3 4)) ;=> (point 4 6)
    (+ (point-3d 1 2 3) (point-3d 4 5 6)) ;=> (point-3d 5 7 9)
    (+ 1 2) ;=> 3
    
  4. To accept multiple arguments, modify (3) as follows

    (define +
      (case-lambda
        [() 0]
        [(x . xs) (foldl add x xs)]))
    
    ;; client's code
    
    (+ (point 1 2) (point 3 4) (point 5 6)) ;=> (point 9 12)
    (+ 1 2 3) ;=> 6
    (+) ;=> 0
    (+ 1) ;=> 1
    (+ (point-3d 1 2 3)) ;=> (point-3d 1 2 3)
    
Division answered 27/4, 2019 at 3:5 Comment(2)
Thanks for the excellent answer! One more question: How can I make the add function accept arbitrarily many arguments? With this implementation it is limited to 2 arguments ...Arletha
See the modified answer above :)Division

© 2022 - 2024 — McMap. All rights reserved.