On Wed, May 6, 2020 at 8:49 PM Jon Zeppieri <zeppi...@gmail.com> wrote:

> It's a bit trickier to define these things in separate files, because
> their definitions refer to each other (though indirectly in this
> case), and the module system does not tolerate cyclic dependencies.
> The most straightforward way to break the cycle would be to take
> advantage of the fact that `table3` and `info-menu-item` each depends
> on `row-edit-menu`, and `info-menu-item` further depends on `table3`,
> but `row-edit-menu` doesn't depend on either. So the dependencies
> aren't really cyclical.

When possible, finding ways to avoid cyclic dependencies in the first place
is definitely advisable: even if you can get Racket to understand them,
mere human beings may find them confusing.

But sometimes there really are entanglements between units of code that you
want to logically separate: then, as Alex said, you need an indirection.
For a relatively simple case, a simple solution using familiar constructs
(like `lambda`) is the right choice, but complicated cycles seem to turn up
especially often in complicated, large-scale code.

Rather than designing an ad hoc system of indirection that can handle all
of the complexity,* I suggest using the one that already exists: units
<https://docs.racket-lang.org/guide/units.html>, Racket's original,
first-class (rather than first-order) module system, offer support for
cyclic dependencies. In fact, they are used in the implementation of
Racket's GUI framework to address precisely this problem.

For this example it ends up being a bit contrived (I would probably use
`class` to define a `row-edit-menu%` that takes a table as an
initialization argument), but here's a way of writing it in a single file
using units: I've posted a Gist
showing the division into multiple files, which is straight-forward.

#lang racket/gui
> (require qresults-list)
> ;; A signatures describes an interface.
> (define-signature row-edit-menu^
>   (row-edit-menu))
> (define-signature table3^
>   (table3))
> (define-signature frame3^ extends table3^
>   (frame3))
> ;; A unit that exports a signature must
> ;; implement the definitions it specifies.
> ;; Units can also import signatures,
> ;; which allows the body of the unit to
> ;; refer to definitions from the imported signatures.
> ;; Before the unit is invoked,
> ;; it must be linked together with other units
> ;; that that export the signatures it imports,
> ;; which will suply the actual implementations.
> ;; Code in the body of the unit is run when the unit is invoked.
> (define-unit row-edit-menu@
>   (import table3^)
>   (export row-edit-menu^)
>   (define row-edit-menu
>     (new popup-menu%))
>   (define info-menu-item
>     (new menu-item%
>          [label "info"]
>          [parent row-edit-menu]
>          [callback
>           (λ (menu-item event)
>             (define num-selected
>               (length (send table3 get-selected-row-indexes)))
>             (message-box "Info"
>                          (~a "You have selected " num-selected " rows")
>                          #f))])))
> (define-unit table3@
>   (import row-edit-menu^)
>   (export frame3^)
>   (init-depend row-edit-menu^)
>   (define frame3
>     (new frame%
>          [label "myTable 3"]
>          [width 800]
>          [height 600]))
>   (define table3
>     (new (class qresults-list%
>            (super-new))
>          [parent frame3]
>          [pref-tag 'preferences-tag]
>          [selection-type 'multiple]
>          [right-click-menu row-edit-menu])))
> ;; Invoke the units and introduce the definitions they
> ;; export into this scope:
> (define-values/invoke-unit/infer
>   (link row-edit-menu@
>         table3@))
> ;; Run the demo:
> (send table3
>       setup-column-defs
>       (let ([column1
>              (λ (data) (vector-ref data 0))]
>             [column2
>              (λ (data) (vector-ref data 1))])
>         (list (qcolumn "Column1" column1 column1)
>               (qcolumn "Column2"
>                        (λ (row)
>                          ;; allows a cell to be blank
>                          (if (number? (column2 row))
>                              (number->string (column2 row))
>                              ""))
>                        column2))))
> (send table3 add-row (vector "R1C1" 10))
> (send table3 add-row (vector "R2C1" 11))
> (send table3 add-row (vector "R3C1" 12))
> (send table3 add-row (vector "R4C1" 13))
> (send frame3 show #t)

* Even when specific requirements eventually led me to implement my own
system, as I discussed a bit in my RacketCon talk, it helped to have used
units and looked at a bit of their implementation. I still do use units in
other parts of the Digital Ricoeur codebase.


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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 

Reply via email to