Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using built-in math operators with custom struct

Tags:

racket

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

like image 552
justinmoon Avatar asked Apr 27 '19 01:04

justinmoon


1 Answers

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)
    
like image 105
Sorawee Porncharoenwase Avatar answered Nov 07 '22 01:11

Sorawee Porncharoenwase