Computed properties for a struct?

74 views
Skip to first unread message

flirora

unread,
Jun 4, 2021, 6:21:52 PM6/4/21
to Racket Users
Is there a way to define a struct so that it has a field whose value is filled in (instead of passed to the constructor) with a value derived from other fields? For example, could you define a struct foo with two explicit fields, x and y, plus a field called z whose value is computed as (+ x y) (yes, simple, but imagine that this is a more expensive operation):

> (define foo1 (foo 1 2))
> (define foo2 (foo 7 12))
> (foo-z foo1)
3
> (foo-z foo2)
19

The closest I could find in the documentation was the #:auto property, but:
  1. it makes the field mutable, even though I'm not interested in mutating z
  2. the default value is fixed across all constructions, while I want z to depend on x and y
Of course, I could make z an explicit field, write a custom constructor, and export that instead of the default constructor for foo. But that seems to be a Royal Pain, especially since (AFAIK) you can't provide the struct as a whole anymore. (And I need to write about 12 of these structs to boot.)

Sorawee Porncharoenwase

unread,
Jun 4, 2021, 8:12:12 PM6/4/21
to flirora, Racket Users

I think you can create a custom accessor, and use macros to avoid the Royal Pain.

One question is, is the derived field expensive to compute? If not, your custom accessor can just compute the output every time it’s called. But if it’s expensive, you can use memoization. Both approaches have the benefit of performing the computation lazily.

#lang racket

(require syntax/parse/define
         (for-syntax racket/syntax))

(define (memoize f)
  (define table (make-hasheq))
  (λ (self) (hash-ref! table self (λ () (f self)))))

(define-syntax-parse-rule
  (provide+define-struct
   name fields
   {~seq #:field custom-accessor-name e} ...)
  #:with (custom-accessor-name* ...)
  (for/list ([n (attribute custom-accessor-name)])
    (format-id #'name "~a-~a" #'name n))
  (begin
    (struct name fields)
    (define custom-accessor-name* (memoize e)) ...
    (provide (struct-out name)
             custom-accessor-name* ...)))

(provide+define-struct
 foo (bar baz)
 #:field bad (λ (self) (+ (foo-bar self) (foo-baz self)))
 #:field bay (λ (self)
               (println 'should-be-called-once)
               (* (foo-bad self) 2)))

(define obj (foo 3 4))
(foo-bay obj)
; 'should-be-called-once
; 14
(foo-bay obj)
; 14

--
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 racket-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/9f9e1548-d01d-469f-b565-22601e02dd82n%40googlegroups.com.

David Storrs

unread,
Jun 7, 2021, 10:23:56 PM6/7/21
to flirora, Racket Users
On Fri, Jun 4, 2021 at 6:21 PM flirora <tko...@gmail.com> wrote:
Is there a way to define a struct so that it has a field whose value is filled in (instead of passed to the constructor) with a value derived from other fields? For example, could you define a struct foo with two explicit fields, x and y, plus a field called z whose value is computed as (+ x y) (yes, simple, but imagine that this is a more expensive operation):

> (define foo1 (foo 1 2))
> (define foo2 (foo 7 12))
> (foo-z foo1)
3
> (foo-z foo2)
19

Yes, with the struct-plus-plus module.  As a bonus you can also typecheck your arguments:

#lang racket

(require struct-plus-plus)
(struct++ foo
          ([x number?] [y number?] [(z #f)])
          (#:rule ("autogenerate z. overrides the provided value"
                   #:transform z (x y) [(+ x y)]))
          #:transparent)

(define foo1 (foo++ #:x 1 #:y 2))
(define foo2 (foo++ #:x 7 #:y 12))

(foo-z foo1) ; 3
(foo-z foo2) ; 19

(define throws-exception (foo++ #:x 7 #:y "string")) ; contract violation
 

Note that the standard positional constructor is generated but does not support the autocalculation feature or typechecking.  To get the advanced features you need to use the keyword version.



The closest I could find in the documentation was the #:auto property, but:
  1. it makes the field mutable, even though I'm not interested in mutating z
  2. the default value is fixed across all constructions, while I want z to depend on x and y
Of course, I could make z an explicit field, write a custom constructor, and export that instead of the default constructor for foo. But that seems to be a Royal Pain, especially since (AFAIK) you can't provide the struct as a whole anymore. (And I need to write about 12 of these structs to boot.)

--
Reply all
Reply to author
Forward
0 new messages