greg
2020-2-21 12:59:07

I’ve found virtual connections often help in scenarios I don’t even realize I have yet. :slightly_smiling_face:


greg
2020-2-21 13:05:40

Seriously: I like any approach that helps with the thankless !@#$-ery of determining exactly in what order/when/how to start/stop various stateful things.

I know, 99% perspiration 1% inspiration, but dang, I want to get to the part where my app does something.


popa.bogdanp
2020-2-21 13:27:43

> determining exactly in what order/when/how to start/stop various stateful things. My approach (which I’ve shamelessly stolen from the Clojure community) is to be extremely explicit about this:

https://docs.racket-lang.org/component/index.html#%28part._.Guide%29

Stateful things have explicit start/stop behaviour. You specify what the dependencies are between things and the system figures out in which order they should be started/stopped.


greg
2020-2-21 13:53:20

I mean, I like both extremes. If there is a reliable way (e.g. virtual connections) to handle init/lifetimes/term from dependencies already expressed in Racket code, that’s wonderful. But if there’s not, yes, I like getting super explicit about it.


greg
2020-2-21 13:54:34

I think the worst case is in the middle. Things like (define worker (thread ___)) as a module level expression? Maybe OK in a tiny app. Often trouble as things grow. Better put in a start-worker function.


greg
2020-2-21 13:56:01

At least that’s my experience.


laurent.orseau
2020-2-21 13:56:27

@mflatt When compiled, are linklets annotated to be pure functional or have side effects, or something of the sort?


popa.bogdanp
2020-2-21 13:57:40

Yup. Same here


mflatt
2020-2-21 13:59:56

No. There is often information about individual exports of the linklet, but not the linklet as a whole.


laurent.orseau
2020-2-21 14:07:51

That’s probably even better. Does that mean that if a function foo(proc) takes as argument a procedure, and on one particular call foo(add1) of the function it happens that the input procedure is a pure function (add1), then the call is marked as ‘pure’?


mflatt
2020-2-21 14:13:24

No, I don’t think that can happen. If foo(add1) is inlined, then maybe the result is detected as pure. But the annotations are definitely not as rich as “this function foo is pure if it receives a pure argument”. More to the point, I don’t think that purity is something that is tracked by any of the compilers right now. The current properties are simpler things, like “is a function that returns a single value” or “is a struct field accessor”.


laurent.orseau
2020-2-21 14:15:21

I see. In principle, then, does purity propagate well, or is it a much hairier problem than that?


laurent.orseau
2020-2-21 14:15:39

It seems that check syntax knows something about it too.


mflatt
2020-2-21 14:18:39

Purity propagates well in the sense that a function that calls a known-pure function can be detected as pure. It’s the higher-order part of foo(add1) that makes things much harder — that is, parameterizing things over purity. I don’t think Check Syntax knows anything about purity, but maybe I misunderstand what you mean.


mflatt
2020-2-21 14:19:54

The schemify layer infers purrity to some degree, but that’s linklet-local. Ditto for the linklet-flattener, where purity is helpful for pruning unused code.


laurent.orseau
2020-2-21 14:20:36

Check-syntax knows if an identifier is ever set!’ed, hence my stretch to purity.


mflatt
2020-2-21 14:21:09

Ah, ok. That’s much simpler than tracking and propagating information about functions.


laurent.orseau
2020-2-21 14:21:21

Ok, so there is still some knowledge about purity then.


laurent.orseau
2020-2-21 14:21:47

and it is used to some extent. That’s cool.


pavpanchekha
2020-2-21 14:52:51

Is there a mechanism for combining together multiple profiler outputs into one?


pavpanchekha
2020-2-21 14:53:17

Right now we run Herbie on a few hundred benchmarks, and have some custom-built aggregation, but it doesn’t extend to the level of individual function calls


pavpanchekha
2020-2-21 14:53:41

It would be good to know how much time Herbie spends, say, evaluating candidate programs


pavpanchekha
2020-2-21 14:54:07

There would also be the possibility of making the profiles finder-grained if aggregation were possible (run a profile per-phase)


samth
2020-2-21 15:05:04

@pavpanchekha there’s not a mechanism that exists currently, but the raw data should be relatively easy to simply combine


pavpanchekha
2020-2-21 15:18:02

Is there a way to save profile data to disk and read it back in a structured format?


pavpanchekha
2020-2-21 15:18:27

I can definitely just dump the runtimes of each function but the caller/callee pointers are sometimes handy


badkins
2020-2-21 15:45:29

I posted a question about a database connection management issue I was having earlier, and as is typical with Racket: 1) A fantastic solution already existed (virtual connections), and 2) A member of the Racket community (thanks @philip.mcgrath) pointed me in the right direction w/in an hour.

I’ve been programming professionally for over thirty years in over eight languages, and I’ve never been as pleased with a language, and it’s community, as I have been w/ Racket.


badkins
2020-2-21 15:48:48

Just to report back, it only took a few minutes to switch to virtual connections, and everything worked perfectly the first time. Very happy!


samth
2020-2-21 16:45:32

@pavpanchekha you can either combine profile? results, or you can combine the (undocumented) sampler output. I don’t know that either one has a trivially-serializable form, but at least profile? values should be easy to serialize.


samth
2020-2-21 16:45:53

Also, adding things to the library itself to serialize/combine these values would be really nice.


pavpanchekha
2020-2-21 18:03:35

Ok, I’ll look into doing that


pavpanchekha
2020-2-21 18:03:54

Combining the values is a first step, but we also would need to trasmit them across Places


shu--hung
2020-2-21 19:18:55

I have a style question. Is it a fine thing to do to export internal functions through submodules for the purpose of writing tests?


shu--hung
2020-2-21 19:20:04

The internal functions have some mutual dependency and it is not easy to pull them into a separate module


dan
2020-2-21 19:27:05

@shu—hung I think parts of the contract system do this, at least I remember doing it when I was implementing collapsible contracts and writing the test cases


shu--hung
2020-2-21 19:32:26

Gotcha. Good to know that this is not a weird thing to do


samdphillips
2020-2-21 19:32:43

A strategy that I have used is to put all implementations into a private directory and use (provide (all-defined-out)) in all of the private modules, and then have the public contracted API reprovided from a different module.


shu--hung
2020-2-21 19:33:08

Right! I forgot about this approach


pavpanchekha
2020-2-21 20:12:12

@samth looks like profile structs can’t be sent from place to place; is there any ugly hack to change that (it’s a graph structure, of serializable things) or should I just write my own?


samth
2020-2-21 20:19:40

I would just change the library to make them either transparent or serializable


leif
2020-2-21 23:14:59

@mflatt Should the following code error:

#lang racket (foo) (define-syntax foo (syntax-rules () [(_) 42]))


leif
2020-2-21 23:15:22

Which seems odd given that this code works:

(module foo '#%kernel (#%require (for-syntax racket/base)) (foo) (define-syntaxes (foo) (syntax-rules () [(_) 42])))


mflatt
2020-2-21 23:30:31

Not ideal, but it’s not clear how to do better. The difference is in the #%module-begins. You can make the former work with an explicit #%expression wrapper, which avoids the partial expansion in #%module-begin that creates the difference.


notjack
2020-2-21 23:52:22

A define-expression-syntax form that automatically added the #%expression wrapper might help. Inserting those wrappers manually is kind of a pain because you have to delay parsing, so you have to make two macros and have one expand to the other inside the wrapper.


ben
2020-2-22 00:30:40

any idea why a free-id-table lookup would fail when the debug-scopes package shows matching scopes? I’m looking for this: "n⁰˙˙³ˢˡⁱ⁼⁴⁺ᵘˢᵉ⁼" in a free-id-table with these keys: ("n⁰˙˙³ˢˡⁱ⁼⁴⁺ᵘˢᵉ⁼")


ben
2020-2-22 00:34:00

oh wow, free-identifier=? returns #true now I’m really confused


ben
2020-2-22 02:14:42

okay I reduced the problem; the lookup fails when there is an outer -let in the code below, but succeeds if there isn’t #lang racket/base (require (for-syntax racket/base syntax/parse syntax/id-table)) (define-for-syntax tbl (make-free-id-table)) (define-for-syntax (lookup n) (free-id-table-ref tbl n #f)) (define-syntax (-define stx) (syntax-parse stx [(_ name:id e) (free-id-table-set! tbl #'name 42) #'(define name e)])) (define-syntax (-let stx) (syntax-parse stx [(_ () e ...) #'(let () e ...)] [(_ ((name:id e0)) e1) (unless (lookup #'e0) (raise-user-error 'not-found)) #'(void)])) ;; OK ;(-define n 3) ;(-let ((m n)) ; (void)) ;; expands to `error: not found` (-let () (-define n 3) (-let ((m n)) (void)))


mflatt
2020-2-22 04:40:31

The identifier really shouldn’t be found, because the binding of the identifer inserted into the table is different (at the time that it is inserted) than the binding at of the identifier that is looked up. The inserted identifier (well, the syntax-local-introduce of that identifier) doesn’t acquire a binding until after the define is expanded. One possible solution to reorder binding and insertion into the table: (define-syntax (-define stx) (syntax-parse stx [(_ name:id e) #'(begin (define name e) (define-syntaxes () (begin (free-id-table-set! tbl #'name 42) (values))))])) That the original -define works at the module level is essentially a hashing accident; it’s like mutating a key used in a hash table, where the mutated key happens to be found because the original and mutated variant have the same hash code. (There are implementation details for free-id tables that make this particular hashing accident consistent, though.)