Understanding prefab. Racket guide says that > Every https://docs.racket-lang.org/guide/define-struct.html#%28tech._prefab%29\|prefab structure type is https://docs.racket-lang.org/guide/define-struct.html#%28tech._transparent%29\|transparent—but even less abstract than a https://docs.racket-lang.org/guide/define-struct.html#%28tech._transparent%29\|transparent type, because instances can be created without any access to a particular structure-type declaration or existing examples. I don’t quite understand. Does it mean that I can create a prefab instance without structure-type declaration?
For example: #lang racket
(struct sprout (kind) #:prefab)
(sprout-kind #s(sprout garlic))
;; => 'garlic
It is OK.
However, #lang racket
(sprout-kind #s(sprout garlic)) ; <---- does it create a instance even if no corresponding struct?
; sprout-kind: undefined;
; cannot reference an identifier before its definition
; in module: "e:\work-pl\racket\code\struct\prefab.rkt"
Why I must (struct sprout (kind) #:prefab)
before using #s(sprout garlic)
? Isn’t (struct sprout (kind) #:prefab)
the structure-type declaration mentioned in guide?
The issue is sprout-kind
, not the prefab syntax. You can effectively create instances of prefabs without having any struct type declarations, but you can’t do much with them in regards to accessing their fields.
I think you can serialize them and such, but to really manipulate them you want to get the accessor functions that struct
defines.
Thanks. So Racket won’t auto create struct type declarations, it is just a instance printable serialized form.
Elements of a prefab struct can still be accessed by index, by converting to a vector: > #s(sprout garlic)
#s(sprout garlic)
> (struct->vector #s(sprout garlic))
#(struct:sprout garlic)
> (vector-ref (struct->vector #s(sprout garlic)) 1)
garlic
Can I define two different auto-fields in struct
? (struct posn (x y [z #:auto] [w #:auto])
#:transparent
#:auto-value ???) ; <----
Guide says that > Specifies a value to be used for all automatic fields in the structure type So no way to define two different auto-fields?
@chansey97 Normal struct declarations are generative. So if you evaluate (struct foo (bar baz))
twice, you’ll get two sets of structs (both named foo
). If you make a (foo 1 2)
with the constructor of the first one, foo-bar
and friends from the second will refuse to work (since they are given structs of an incorrect type).
If you ever see an error such “foo-bar: expected a foo
structure, given: (foo 1 2)” you’ll know what to look for.
In contrast prefab structures aren’t generative. You have have a single constructor.
Thanks. Is struct first-class value? (struct posn (x y) #:transparent)
(define (raven-constructor super-type)
(struct raven (name)
#:super super-type
#:transparent)
raven)
(let ((s (raven-constructor struct:posn)))
(let ((instance (s 1 2 "hello")))
(posn-x instance) ; <---- ok
(raven-name instance) ; <--- raven-name: undefined
))
The return value of struct
expression is a constructor procedure (not the struct itself), but how can I get name field of instance
.
I found that we have a global identifier struct:posn
, but no struct:raven
in this example.
IIRC, the raven-constructor
here is a bit like mixin mechanism in racket/class
.
#lang racket
(struct posn (x y) #:transparent)
(define (raven-constructor super-type)
(struct raven (name)
#:super super-type
#:transparent)
(values raven struct:raven))
(let-values ([(s st) (raven-constructor struct:posn)])
(define-values (name init-field-count auto-field-count
acc-proc mut-proc
imm-k-list super-type skipped?)
(struct-type-info st))
(define (raven-name r) (acc-proc r 0))
(displayln (list name init-field-count auto-field-count acc-proc mut-proc
mut-proc imm-k-list super-type skipped?))
(let ((instance (s 1 2 "hello")))
(posn-x instance) ; <---- ok
(raven-name instance) ; <--- raven-name: undefined
))
Alternatively, let raven-construtor
return the functions you need: (struct posn (x y) #:transparent)
(define (raven-constructor super-type)
(struct raven (name)
#:super super-type
#:transparent)
(values raven raven-name))
(let-values ([(s raven-name) (raven-constructor struct:posn)])
(let ((instance (s 1 2 "hello")))
(posn-x instance) ; <---- ok
(raven-name instance) ; <--- raven-name: undefined
))
But it depends on the situation.
@soegaard2 Very helpful, thanks!
Also check out make-struct-type
if you want a procedural interface to structs https://docs.racket-lang.org/reference/creatingmorestructs.html#%28def._%28%28quote._~23~25kernel%29._make-struct-type%29%29
BTW, what is the struct:
in struct:raven
? Does it mean that Racket has a namespace struct
and since #<struct-type:raven>
is in that namespace, so we must prefix struct:
?
It’s not a namespace. The name struct:raven
is just a standard identifier.
An interesting fact: struct:id
is a structure type descriptor, which is a variable binding in runtime, the value of that binding is a first-class value. gen:id
is a transformer binding, which can not be referred in runtime. I guess it only exists in macro expand time.
Mini-quizz: There’s a simpler definition of pair->list
than (define (pair->list p) (list (car p) (cdr p)))
using primitives, for pairs of non-pairs. Will you find it? :slightly_smiling_face:
(This is surely wellknown by long time schemers of course)
(please reply in thread to avoid spoilers)
(define pair->list flatten)
except that it doesn’t quite work if the car
or cdr
also contains pairs / lists.
True
Wait, so is that the solution you have in mind?
Sure. Anything else will require more characters than the original definition of course.
(the caveat is important obviously)
I often need to go from assocs of numbers to lists, in which case flatten is pretty handy
Is there a way to force Racket to print the content of a struct instance? For example, (struct cake (x y)
#:transparent
#:methods gen:custom-write
[(define (write-proc self port mode)
(fprintf port "This is my cake"))]
)
(cake 1 2)
;; This is my cake
But in some situation, I hope it print (cake 1 2)
because it is easily to debug. Currently I have to modify struct cake
.
If it’s transparent, perhaps try stuff like struct->list
/ struct->vector
?
I don’t get it. If you just have #:transparent
and no gen:custom-write
it should print the contents?
My read is that in the final product, @chansey97 wants the gen:custon-write
behavior. But for debugging purpose, it’s nice to see the content.
Ah.
I don’t want to modify source code of the project.
Or I have to use field-getter (but sometime’s you even hardly know the concrete struct type…). Of course, big project usually doesn’t use #:transparent
, It is just a small example.
Speaking of #:transparent
. I’d like to make my structs non-transparent for better encapsulation, but in the end I always make them transparent because I can compare two structs with equal?
in automated tests.
Is there a not-too-obscure workaround to have the cake (better encapsulation) and it eat it (easy comparison in tests)? :slightly_smiling_face:
BTW, non-transparent struct also can be pattern match, that is surprising.
Typically, encapsulation means we can arbitrarily change fields’ order, i.e. we can not do pattern matching.
encapsulation is overrated. Also, it’s annoying when one wants to build on top of a lib. It’s like unnecessary obfuscation. I remember mflatt saying that one mistake in struct
was to not be #:transparent
by default.
A peculiar feature of match in Racket: #lang racket
(struct my-struct (x) #:transparent)
(define (my-integer? x)
(printf "my-integer? x=~a\n" x)
(integer? x))
(define (my-add10 x)
(printf "my-add10 x=~a\n" x)
(+ x 10))
(match (my-struct 1)
[(my-struct (app my-add10 (? my-integer? x)))
(printf "x=~a\n" x)])
;; my-add10 x=1
;; my-integer? x=11
;; x=11
When matching, the value will first call my-add10
(e.g. do some data transformation), then call predicate my-integer?
.
Right, because the semantics of (app f pats…)
patterns are (something like) “match a single value, apply f
to it, and match the result with pats…
. It looks weird because of the nested parens (we think inner-evaluation first), but it’s a match pattern, so the semantics are different.
In Rhombus, IIUC, there will be both class
and Map
. Map
is essentially transparent struct
.