Replies: 3 comments 5 replies
-
I just thought of a statically typed version that may be better. Tac as a top-level effectWe allow writing programs with
This program has top-level effect Implementing T.refInternally to the compiler, When evaluating For For Event handlersSeparately from these references, we provide primitives in the compiler to register event handlers. E.g., we can have
For the same language of event_handler described above. ExampleSo, the revised example becomes
If you want to reset the reference periodically, you can also do
This looks nicer to me. Though it relies on a type unsafe |
Beta Was this translation helpful? Give feedback.
-
Hi Nik, Your second proposal with a
|
Beta Was this translation helpful? Give feedback.
-
At today's F* meeting, we discussed this at length. Despite our initial enthusiasm for the typed references version of this proposal, the introduction of top-level compile-time effects raises many potential concerns, including things like how these top-level effects interact with checked files etc. In contrast, the dynamically typed proposal side steps a lot of these issues, at the cost of user strings as keys and requiring a dynamic check when reading the store. This additional check may be a small price to pay in comparison to the complexity of the implementing the typed references version (which saves the check by using a magic in the compiler). |
Beta Was this translation helpful? Give feedback.
-
Motivation
It is often useful in metaprograms to have non-local state.
For a very simple example, say I have a tactic
pp
used topostprocess definitions and I want to
pp
to be able to maintain acounter to read how many times it has been invoked so far, and to
increment that counter.
Currently, there is no clean way to do this in Meta-F*, since each
invocation of
pp
runs in an initial fresh initial state with justthe proofstate to manipulate.
For a more realistic example, and the example that really precipated
this note, currently the typeclass resolution tactics have no access
to state of this form, and so cannot make use of memoization tables to
cache typeclass resolution results. So, if you have a program with a
100 invocations of (+), the tactic will resolve it from scratch 100
times, which makes typeclasses impractical to use at non-trivial
scale.
Proposal: A Dynamically Typed Registry of Stateful Objects
I propose to add support for a a key-value store (which I'll call a
registry) implemented natively in the compiler and expose primitives
to metaprograms to manipulate this resgistry. The registry maps string
keys to values of an extensible variant type called
ext
. Associatedwith each entry in the registry is also an event handler, which I'll
explain shortly.
The main primitives exposed to a metaprogram are:
The first primitive,
fetch_or_initialize_registry_key k i h
is usedby a metaprogram to read the current value of the key
k
from theregistry. If the
k
is not present, the registry is updated toassociate the value
i
(and the event handlerh
) withk
andi
is returned.
The type of the values is
ext
, which is an alias for the onlyextensible variant type in F*, i.e.,
exn
.The use an extesible variant here enables client to store values of
any type in the registry, though they will have to check on retrieving
a value that it has the tag they expect---this is what makes it
dynamically typed.
The
update_registry k v
call updates the value associated withk
to
v
, failing ifk
is not already present in the registry.Here's a sample usage for a tactic that uses a counter:
Event handlers
MyCounterTac
expects its state to persist indefinitely. However, forother tactics, it may be useful to reset the state at some specific
intervals. For example, for the typeclass resolution tactic, one could
imagine that we want to reset any memoization tables it uses every
time F* finishes processing a top-level definition.
For that purpose, we define a small language of events and their
handlers. For example:
If when an
AtEveryTopLevel h
is registered as an event handler for akey, then every time F* states processing a top-level definition, it
will invoke
h()
---this can, e.g., reset the state for that key bycalling
update_registry_key k initial_value_for_k
.Why dynamically typed?
One main criticism of this proposal is that it is dynamically typed.
A statically typed registry is much harder to achieve and even if it
works, it may be harder to use for a metaprogram client rather than
just adding a dynamic check.
For example, one may wonder why not model this non-local state as a
heap addressed by typed references. E.g., why not have an interface like:
This is certainly doable for local state. But, for state that persists
across calls to tactics, we need a way for a tactic
p
to allocate ar:ref t
, to persist that reference somewhere, and then for anothertactic invocation
q
to read that reference---but, then we're back towhere we started. Where should the
r:ref t
be persisted?One could imagine other approaches, including various kinds of
dependently typed encodings, perhaps the use of monotonic state to
model references that remain live etc., though these are way more
sophisticated and perhaps not as valuable for metaprograms, which
typically can fail anyway.
Beta Was this translation helpful? Give feedback.
All reactions