What is the difference between a transparent and a prefab struct?
Asked Answered
D

4

13

As the title implies, I don't understand the difference between using #:transparent and using #:prefab when defining a struct. The reference mentions that prefab involves some sort of global sharing.

What is the difference between them? In which situation should I use one over the other?

Drawback answered 19/8, 2015 at 14:26 Comment(0)
D
11

To make a structure type transparent, use the #:transparent keyword after the field-name sequence:

(struct posn (x y)
        #:transparent)

> (posn 1 2)
(posn 1 2)

An instance of a transparent structure type prints like a call to the constructor, so that it shows the structures field values. A transparent structure type also allows reflective operations, such as struct? and struct-info, to be used on its instances.

Although a transparent structure type prints in a way that shows its content, the printed form of the structure cannot be used in an expression to get the structure back, unlike the printed form of a number, string, symbol, or list.

A prefab (“previously fabricated”) structure type is a built-in type that is known to the Racket printer and expression reader. Infinitely many such types exist, and they are indexed by name, field count, supertype, and other such details. The printed form of a prefab structure is similar to a vector, but it starts #s instead of just #, and the first element in the printed form is the prefab structure type’s name.

Lastly, I think you may need using #:transparent most of the time over #:prefab ,based on my experience I usually use #:transparent.

Denial answered 19/8, 2015 at 14:35 Comment(3)
In addition to "exotic" reflective ops, a very basic thing only works with #:transparent structs -- equal?. Given (struct x (y)) (equal? (x 1) (x 1)) is #f! It's only #t with #:transparent.Tetrapody
In a sense, #:transparent is the "warm bowl of porridge" in between opaque and prefab. In hindsight it should have been the default, with #:opaque and #:prefab modifiers, IMHO.Tetrapody
This answer should probably link to the docs where the text is from.Apeak
A
8

To extend the other answers and give some more examples for the second part of the question:

#lang racket

(struct A (x y))
(displayln (A 1 2)) ; => #<A>
(equal? (A 1 2) (A 1 2)) ; => #f
;(equal? (A 1 2) (read (open-input-string (~a (A 1 2))))) ; => ERR: bad syntax

(struct B (x y) #:transparent)
(displayln (B 3 4)) ; => #(struct:B 3 4)
(equal? (B 3 4) (B 3 4)) ; => #t
(equal? (B 3 4) (read (open-input-string (~a (B 3 4))))) ; => #f

(struct C (x y) #:prefab)
(displayln (C 5 6)) ; => #s(C 5 6)
(equal? (C 5 6) (C 5 6)) ; => #t
(equal? (C 5 6) (read (open-input-string (~a (C 5 6))))) ; => #t
  • Use opaque structs if you want to enforce the struct abstraction with no exceptions, i.e., the struct can only be created and inspected with the accessors.
  • Use transparent structs to give access to the printer and equal?.
  • Use prefab structs if you want to do serialization, e.g. when writing and reading from disk.
Apeak answered 19/8, 2015 at 17:23 Comment(0)
I
7

The racket guide probably has a more gentle introduction to prefab structs.

The biggest difference is that a transparent struct still requires the struct constructor to create one of them.

For example, given the following struct definition:

(struct foo (a b) #:prefab)

The following are two ways to create the exact same struct.

> (foo 1 2)
'#s(foo 1 2)
> #s(foo 1 2)
'#s(foo 1 2)

This means that any racket module can create a foo prefab struct, even without it being defined first. This makes it very useful if you want to put it in a macro, or send it to a separate instance of racket running on a different machine.

Generally, I would recommend going with #:transparent structs unless you need the full power of a #:prefab struct.

Intradermal answered 19/8, 2015 at 14:33 Comment(0)
R
6

One other important detail not mentioned yet: transparent (and normal) structs are generative. This means if you define the same struct twice, values created with the first instance of the struct definition are not equal? to values created with the second definition. You can see this for yourself in a REPL session:

> (struct posn (x y) #:transparent)
> (define origin1 (posn 0 0))
> (struct posn (x y) #:transparent)
> (define origin2 (posn 0 0))
> (equal? origin1 origin2)
#f

Despite being the same definition and having the same content, the two instances are not equal?. Although the structs are transparent they are considered separate definitions for the reason Leif Anderson pointed out, just using #:transparent still requires that the only way to create the struct be with the constructor the struct form defines. Two definitions means two different constructors.

However, with prefab structures, that limitation goes away - you can create prefab structs yourself by just writing the reader form of them like #s(posn 0 0). There's no longer a reason to require all instances of the struct be created with its defined constructor, and thus no reason two different but identical struct definitions wouldn't recognize each other:

> (struct posn (x y) #:prefab)
> (define origin1 (posn 0 0))
> (struct posn (x y) #:prefab)
> (define origin2 (posn 0 0))
> (equal? origin1 origin2)
#t
> (equal? origin1 #s(posn 0 0))
#t

I am of the opinion that structs that represent just some raw data pooled together should be prefab to get free, easy, and safe serialization, structs that have restrictions on how they can be constructed should be transparent, and structs that should encapsulate some behavior and hide information should be neither transparent nor prefab. These are mere guidelines however, your mileage may vary.

Rola answered 21/8, 2015 at 9:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.