#lang languages and cyclic dependencies

71 views
Skip to first unread message

Philip McGrath

unread,
Apr 30, 2017, 9:03:45 PM4/30/17
to Racket Users
I'm working on a #lang for configuration files for a larger project I'm working on, and I'm trying to find a setup that meets my (largely cosmetic) goals without producing a "standard-module-name-resolver: cycle in loading" error.

Given the following directory structure:
  • my-project/
    • config.rkt
    • config/
      • local.rkt
      • production.rkt
I would like both to write "local.rkt" and "production.rkt" in "#lang my-project/config" and to have "(require my-project/config)" provide bindings re-exported from "local.rkt" and "production.rkt" and some extra bindings for working with those values.

This seems like it should be doable, because there aren't any logical cyclic dependencies, but I haven't found a way to convince Racket of that. 

I initially tried making a "my-project/config/lang/" directory with a "module-lang.rkt" and a "reader.rkt" consisting of "(module reader syntax/module-reader my-project/config/lang/module-language)", then having "config.rkt" require and re-export "local.rkt", "production.rkt", and the appropriate exports of "module-lang.rkt", but this gave me a "cycle in loading" error.

My first guess was that the problem might be that Racket was looking for a reader submodule of "config.rkt", so I re-wrote "module-lang.rkt" and "reader.rkt" as submodules of "config.rkt" (with "module", not "module*" or "module+"), but this didn't solve the problem.

Is there a way to do what I want? 

-Philip

Alex Harsanyi

unread,
May 1, 2017, 3:59:59 AM5/1/17
to Racket Users
Hi Philip,

I don't have an answer to your problem, but I'm curious as to what do you store in "local.rkt" and "production.rkt" to justify such a complicated solution.

In the projects that I worked on (Racket or otherwise), local vs production differ in the values for different parameters, which are just key => value mappings. In Racket, I would just store these as association lists in "local.rktd" and "production.rktd" than in the code just to a:

(define config-file (if (prodution?) "production.rktd" "local.rktd"))
(define config (call-with-input-file config-file read))

Than just use `assoc` to find the values for the parameters.

Best Regards,
Alex.

Philip McGrath

unread,
May 1, 2017, 8:01:33 AM5/1/17
to Alex Harsanyi, Racket Users
I have often done it that way, too. In this case I decided to use a #lang for a few reasons:
  • The values for some of the parameters are not readable.
  • In some cases I want to do a little bit of work to calculate the value. For instance, "production.rkt" reads in the values of some API keys from a file not tracked in the git repository.
  • I have more than just two different configurations, e.g. for local automated testing vs. interactive local use.
  • I have some colleagues working on other aspects of the project who have little to no Racket experience, but who may need to edit the configuration files. Especially with syntax-parse, making a small #lang gets some free IDE support and good, early error messages if e.g. they enter a string where there should have been a number. (Plus, it does show off how wonderful Racket is.)
The #lang essentially just redefines #%module-begin so that the module defines and exports a function "config" (actually a struct with prop:procedure) that calls a thunk with the intended parameterization.

Ultimately I actually found that writing a #lang was fairly comparable to the amount of code I would have needed to get good error reporting with the read-based option, so I'm pretty happy with it.

Philip


--
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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Philip McGrath

unread,
Nov 28, 2017, 4:28:06 PM11/28/17
to Alex Harsanyi, Racket Users
For the benefit of posterity, while I haven't actually solved the cycle-in-loading error, I was able to get the results that I wanted in this case by:
  1. Implementing the language as a `module` (not `module*` or `module+`) submodule of "my-project/config.rkt", including a
    (module reader syntax/module-reader
      #:language '(submod my-project/config lang))
  2. In the body of the outer `my-project/config` module, instead of requiring the values from "my-project/config/local.rkt" etc. as usual, getting them lazily/dynamically, which I am doing with a little wrapper on top of `define-runtime-module-path-index`, though `racket/lazy-require` would probably also work well.
Evaluating the configuration files lazily was particularly good for me because it lets me write, say, "production.rkt" assuming that various things from the production server exist and just raising an error if not, rather than having to make everything in every configuration file run on every machine.

-Philip

Jack Firth

unread,
Nov 28, 2017, 11:58:37 PM11/28/17
to Racket Users
Huh, I must have missed this the first time around. I've run into this sort of thing multiple times before and it usually brings me to this question:

What if the configuration file was the main module of your app, instead of something imported by your app?

Think about it. The "my-project/config/local" and "my-project/config/production" modules are essentially saying "to run the app locally, set the project config to these values". What module could possibly have a use for that information other than the main "app startup" module? It seems like all other code should only depend on modules that define the schema of configuration, rather than any modules that set that configuration to specific values. It seems odd for the schema definition module (my-project/config) to depend on modules that actually use that schema to set config values (my-project/config/local). The reverse sounds more sensible.

If you make a module written in `#lang my-project/config` expand to a `main` submodule that starts up your app you get some other neat Rackety things to show off to your colleagues: not only will they get an IDE with syntax highlighting and good error messages "for free",  pressing the Run button with a config file open will launch the app automatically! This might not work if launching your app is especially complicated, but I'm curious if you've explored this direction and what your thoughts are.

Tony Garnock-Jones

unread,
Nov 29, 2017, 9:36:06 AM11/29/17
to Jack Firth, Racket Users
On 11/28/2017 11:58 PM, Jack Firth wrote:
> What if the configuration file was the main module of your app, instead
> of something imported by your app?

I've tried this, and it works well. I haven't used a custom config #lang
yet, but even with regular old Racket, it works very well.

For example:
https://github.com/tonyg/racket-pkg-website/blob/master/configs/tonyg.rkt

Cheers,
Tony

Neil Van Dyke

unread,
Nov 29, 2017, 9:47:13 AM11/29/17
to Racket Users


> On 11/28/2017 11:58 PM, Jack Firth wrote:
>> What if the configuration file was the main module of your app, instead
>> of something imported by your app?

If you want to combine this with per-user customization in Racket code,
the 3rd message in the following thread suggests one way to handle
executables smoothly:

Subject: low-friction app extensibility in racket
Date: Thu, 6 Jul 2017 05:53:40 -0400
https://groups.google.com/forum/#!topic/racket-users/6hn9J-r0Nek

Reply all
Reply to author
Forward
0 new messages