To make your vector+ operation take an arbitrary number of arguments, this
is what I'd do:
(define (vector+ . vectors)
(vector
(for/sum ([v (in-list vectors)]) (vector-i v))
(for/sum ([v (in-list vectors)]) (vector-j v))
(for/sum ([v (in-list vectors)]) (vector-k v))))
(module+ test
(test-case "vector+"
(check-equal? (vector+) (vector 0 0 0))
(check-equal? (vector+ (vector 1 2 3)) (vector 1 2 3))
(check-equal? (vector+ (vector 1 2 3) (vector 4 5 6)) (vector 5 7 9))))
I can see some value in abstracting over this, if you've got a *lot* of
structs with mathy variable-arity operations on them. I personally might
try to do something with reducers
<https://docs.racket-lang.org/rebellion/Reducers.html>. I'm not fully sure
how I'd go about it though. Rather than experiment with that I'd probably
just prefer to write the boring boilerplate code.
On Monday, February 1, 2021 at 6:26:20 PM UTC-8 making-a-racket wrote:
> Turns out that I don't see a way to calculate the #:auto-value using the
> constructor fields, so I don't see how to make something like
>
> (struct test (x y z [data #:auto])
> #:auto-value #(x y z))
>
> work.
>
> So I've decided to go with something like this:
>
> (struct color (r g b) #:transparent)
> (struct vector (i j k) #:transparent)
> (struct point (x y z) #:transparent)
>
> (define/contract (tuple-map-elementwise proc tuple)
> (-> (-> real? real?) (or/c vector? point? color?) (or/c vector? point?
> color?))
> (match tuple
> [(struct vector (i j k)) (vector (proc (vector-i tuple))
> (proc (vector-j tuple))
> (proc (vector-k tuple)))]
> [(struct point (x y z)) (point (proc (point-x tuple))
> (proc (point-y tuple))
> (proc (point-z tuple)))]
> [(struct color (r g b)) (color (proc (color-r tuple))
> (proc (color-g tuple))
> (proc (color-b tuple)))]))
>
> (define/contract (tuple-map-pairwise proc tuple1 tuple2)
> (-> (-> real? real? real?) (or/c vector? point? color?) (or/c vector?
> point? color?) (or/c vector? point? color?))
> (match (list tuple1 tuple2)
> [(list (struct vector (i1 j1 k1)) (struct vector (i2 j2 k2)))
> (vector (proc (vector-i tuple1) (vector-i tuple2))
> (proc (vector-j tuple1) (vector-j tuple2))
> (proc (vector-k tuple1) (vector-k tuple2)))]
> [(list (struct point (x1 y1 z1)) (struct point (x2 y2 z2)))
> (point (proc (point-x tuple1) (point-x tuple2))
> (proc (point-y tuple1) (point-y tuple2))
> (proc (point-z tuple1) (point-z tuple2)))]
> [(list (struct color (r1 g1 b1)) (struct color (r2 g2 b2)))
> (color (proc (color-r tuple1) (color-r tuple2))
> (proc (color-g tuple1) (color-g tuple2))
> (proc (color-b tuple1) (color-b tuple2)))]))
>
> (define (tuple-op-by-constant op tuple c)
> (tuple-map-elementwise (λ (e) (op e c)) tuple))
>
> (define (tuple-op-pairwise op t1 t2 . tuples)
> (cond [(empty? tuples) (tuple-map-pairwise op t1 t2)]
> [else (apply tuple-op-pairwise op
> (tuple-map-pairwise op t1 t2)
> (car tuples)
> (cdr tuples))]))
>
> (define (vector+c v c)
> (tuple-op-by-constant + v c))
>
> (define (vector*c v c)
> (tuple-op-by-constant * v c))
>
> (define (vector+ u v . vectors)
> (apply tuple-op-pairwise + u v vectors))
>
> (define (vector- u v . vectors)
> (apply tuple-op-pairwise - u v vectors))
>
> Then I'll do something similar for points and colors. This library will
> probably be converted to Typed Racket, so the contracts are only temporary.
>
> This seems to be the cleanest solution I think, and it's actually quite
> similar to the F# code I've written for the same project.
>
> I know this seems simple, but wanted to check on people's thoughts on
> idiomatic ways in Racket, and to make sure I wasn't missing some secret
> sauce of structs or something else.
>
>
> On Monday, February 1, 2021 at 4:38:37 PM UTC-6 making-a-racket wrote:
>
>> Apologies. By fourth optional argument, I meant a fourth field with the
>> #auto field option. I'm experimenting with this now.
>>
>> On Monday, February 1, 2021 at 3:24:49 PM UTC-6 making-a-racket wrote:
>>
>>> Thanks for the suggestion and for the macro implementation. I'll have to
>>> pour over that a bit.
>>>
>>> I wanted to do map because I wanted to make it easy to idiomatically
>>> implement addition and other such operators on my data types such that they
>>> accept arbitrary amounts of arguments and provide the map for other uses.
>>>
>>> So that's why the vector (in the Racket sense) is the most simple option
>>> in that respect, since I can trivially do:
>>>
>>> (define (sum-vector v)
>>> (apply + (vector->list v)))
>>>
>>> (define (vector+ . vs)
>>> (apply vector-map + vs))
>>>
>>> (define (vector-i v)
>>> (vector-ref v 0))
>>> ;; and so on
>>>
>>> The only think I don't get there are my wanted datatypes and associated
>>> predicates, since vectors, points, and colors would all be Racket vectors.
>>>
>>> I could almost do structs with a fourth optional argument that holds a
>>> Racket vector that never gets used explicitly by the "user" and build
>>> helper functions to properly update it, which is then used to build all the
>>> operator and other such functions.
>>>
>>> If I just do structs as I originally notated, how do you suggest I
>>> implement things like vector+ to take in arbitrary amounts of arguments?
>>>
>>>
>>> On Monday, February 1, 2021 at 12:48:19 AM UTC-6 [email protected]
>>> wrote:
>>>
>>>> I'd suggest just going with the structs and making them transparent.
>>>> It's only three structs and only with a handful of fields, abstracting
>>>> over
>>>> them with map and fold doesn't seem worth the added complexity IMO. But if
>>>> you'd really like to map and fold over structs, I highly recommend using
>>>> macros, `syntax-parse` and the struct-id
>>>> <https://docs.racket-lang.org/syntax-classes/index.html?q=struct-id#%28form._%28%28lib._syntax%2Fparse%2Fclass%2Fstruct-id..rkt%29._struct-id%29%29>
>>>>
>>>> syntax class to do so:
>>>>
>>>> (require (for-syntax syntax/parse/class/struct-id)
>>>> syntax/parse/define)
>>>>
>>>> (define-simple-macro (struct-map type:struct-id instance-expr:expr
>>>> map-function-expr:expr)
>>>> (let ([map-function map-function-expr]
>>>> [instance instance-expr])
>>>> (type.constructor-id (map-function (type.accessor-id instance))
>>>> ...)))
>>>>
>>>> (struct point (x y z) #:transparent)
>>>>
>>>> ;; Outputs (point 2 3 4)
>>>> (struct-map point (point 1 2 3) add1)
>>>> On Sunday, January 31, 2021 at 4:20:03 PM UTC-8 making-a-racket wrote:
>>>>
>>>>> Hello. I have a project where I am needing to represent vectors (in
>>>>> the mathematical sense), points, and colors. Both the vectors and points
>>>>> will be 3D. I'm having trouble knowing what's an idiomatic way to
>>>>> represent
>>>>> and interact with data types that are similar but different.
>>>>>
>>>>> In general, I could simply all represent them with a list or vector
>>>>> (in the Racket sense). So I could have:
>>>>>
>>>>> (define (vector i j k) #(i j k))
>>>>> (define (point x y z) #(x y z))
>>>>>
>>>>> Then I could readily use the existing vector functions, such as
>>>>> vector-map without having to define my own. But I don't super like this
>>>>> because I have to define my own accessor functions like vector-i and
>>>>> point-y and also don't get predicates like vector? for free.
>>>>>
>>>>> Another way is that I could use structs, but then I'm stuck
>>>>> implementing things myself and across the structs. To avoid the latter
>>>>> point, I could use pattern matching. So something like:
>>>>>
>>>>> (struct vector (i j k))
>>>>> (struct point (x y z))
>>>>>
>>>>> (define (tuple-map proc tuple)
>>>>> (match tuple
>>>>> [(struct vector (i j k)) (vector (proc (vector-i tuple))
>>>>> (proc (vector-j tuple))
>>>>> (proc (vector-k tuple)))]
>>>>> [(struct point (x y z)) (point (proc (point-x tuple))
>>>>> (proc (point-y tuple))
>>>>> (proc (point-z tuple)))]
>>>>> [(struct color (r g b)) (color (proc (color-r tuple))
>>>>> (proc (color-g tuple))
>>>>> (proc (color-b tuple)))]))
>>>>>
>>>>> But of course, this map doesn't take multiple tuples. And this feels
>>>>> awkward, because I'll need to implement other things, like fold. Map and
>>>>> fold would be used in defining new operators on vectors and points, like
>>>>> addition, normalization (for vectors only), etc.
>>>>>
>>>>> The ideal thing would be that I could define a struct for these types,
>>>>> that had the accessor functions like vector-i and predicates likes
>>>>> vector? but was actually represented by a vector (in the Racket
>>>>> sense) underneath the hood. Does something like this exist in Racket (not
>>>>> classes please).
>>>>>
>>>>> In F#, I did this same thing using F#'s records for the vector, point,
>>>>> and color data types, and they inherited an ITuple interface (F#'s
>>>>> immutable, functional data types can implement interfaces). Can Racket's
>>>>> stucts inherit from interfaces? Is there something I can do with generics?
>>>>>
>>>>> Thanks for any help on this design.
>>>>>
>>>>
--
You received this message because you are subscribed to the Google Groups
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/racket-users/e0925bff-c910-4cf2-b58f-810df2d28f58n%40googlegroups.com.