nico7et8
nico7et8

Reputation: 227

Defining a "minus function" for vectors in typed/racket

New to Racket. I've been trying to make a simple vector struct and define common vector functions (dot product, norm...). I am aware of math/matrix but do not wish to use it for now.

Everything's going great until I tried to create a "minus function" to subtract vectors. The addition function works fine, but the subtraction raises typecheck errors. Here's the code :

#lang typed/racket

(struct: vect ([x : Real]
               [y : Real])
  #:transparent)

(: vect+ (-> vect * vect))
(define (vect+ . vs)
  (vect (apply + (map vect-x vs))
        (apply + (map vect-y vs))))

(: vect- (-> vect * vect))
(define (vect- . vs)
  (vect (apply - (map vect-x vs))
        (apply - (map vect-y vs))))

(vect+ (vect 1 2) (vect -3 1) (vect 0 4))

(vect- (vect 1 2) (vect -3 1) (vect 0 4))

The vect+ (vector addition) function works fine, but the vect- function raises :

. Type Checker: Bad arguments to function in `apply':
Domains: Number Number *
Arguments: (Listof Real) *
 in: (apply - (map vect-x vs))
. Type Checker: Bad arguments to function in `apply':
Domains: Number Number *
Arguments: (Listof Real) *
 in: (apply - (map vect-y vs))
. Type Checker: Summary: 2 errors encountered in:
  (apply - (map vect-x vs))
  (apply - (map vect-y vs))

Why would "minus" refuse reals when "plus" takes them and the following line works fine ?

    (apply - (list 1 2 3))
    ; -> -4

Upvotes: 2

Views: 315

Answers (3)

Alex Knauth
Alex Knauth

Reputation: 8373

(Building off of stchang's answer) It works if you change the definition of vect- to this:

(: vect- (-> vect vect * vect))
(define (vect- v . vs)
  (vect (apply - (vect-x v) (map vect-x vs))
        (apply - (vect-y v) (map vect-y vs))))

This is exactly because - doesn't take zero arguments. Because of that, apply needs some reassurance that there will be at least one argument.

Upvotes: 2

stchang
stchang

Reputation: 2540

The error is because apply expects its first argument to be a function that accepts zero-or-more arguments, but - requires one-or-more arguments (note that + works because it accepts zero arguments).

> (:print-type apply)
(All (a b) (-> (-> a * b) (Listof a) b))
> (:print-type -)
; big output omitted, 
; but notice it includes (-> Number Number * Number),
; but not (-> Number * Number))
> (-)
. Type Checker: could not apply function;
 wrong number of arguments provided
  expected at least: 1
  given: 0 in: (-)
> (+)
- : Integer [more precisely: Zero]
0

To get around this, you can cast apply to a function that accepts a function of one-or-more as its first argument:

(: vect- (-> vect vect * vect))
(define (vect- . vs)
  (define apply1+
    (cast apply (All (a b) (-> (-> a a * b) (Listof a) b))))
  (vect (apply1+ - (map vect-x vs))
        (apply1+ - (map vect-y vs))))

(vect- (vect 1 2) (vect -3 1) (vect 0 4)) ; (vect 4 -3)

Make sure to update the type of vect- accordingly.

OR, you can define an alternative subtraction function that accepts zero arguments:

(: new- (-> Real * Real))
(define (new- . args)
  (if (empty? args)
      0
      (apply - args)))

(: vect- (-> vect * vect))
(define (vect- . vs)
  (vect (apply new- (map vect-x vs))
        (apply new- (map vect-y vs))))

Here, it's ok to give vect- zero arguments.

Upvotes: 2

nico7et8
nico7et8

Reputation: 227

My guess is subtraction works differently since :

    #lang racket
    (- 5)             ; -> -5
    (- 5 3)           ; -> 2

So I defined a neg function which gives the opposite of a vector and defined a syntax for vect-. Pretty sure it's an overkill but don't I don't know how to do it more simply. So adding this to the code works :

(: neg (-> vect vect))
(define (neg v)
  (vect (- (vect-x v))
        (- (vect-y v))))

(define-syntax vect-
  (syntax-rules ()
    [(vect- v) (neg v)]
    [(vect- v1 v2 ...) (apply vect+ (list* v1 (map neg (list v2 ...))))]))

... but I'm pretty sure there's a MUCH better way.

Upvotes: 1

Related Questions