pax_global_header00006660000000000000000000000064145075714620014525gustar00rootroot0000000000000052 comment=50e45e9f4ed52840427c3bfddc9037a645d9bf19 coq-record-update-0.3.3/000077500000000000000000000000001450757146200150465ustar00rootroot00000000000000coq-record-update-0.3.3/.gitattributes000066400000000000000000000000401450757146200177330ustar00rootroot00000000000000* text=auto *.sh text eol=lf coq-record-update-0.3.3/.github/000077500000000000000000000000001450757146200164065ustar00rootroot00000000000000coq-record-update-0.3.3/.github/workflows/000077500000000000000000000000001450757146200204435ustar00rootroot00000000000000coq-record-update-0.3.3/.github/workflows/coq-action.yml000066400000000000000000000013451450757146200232260ustar00rootroot00000000000000name: CI on: push: branches: - master tags: '*' pull_request: schedule: # Tuesday 8am UTC (3am EST) - cron: '0 8 * * TUE' jobs: build: runs-on: ubuntu-latest strategy: matrix: coq_image: - 'dev-ocaml-4.14-flambda' - '8.18' - '8.17' - '8.16' - '8.15' - '8.14' fail-fast: false steps: - uses: actions/checkout@v3 - uses: coq-community/docker-coq-action@v1 with: opam_file: 'coq-record-update.opam' custom_image: coqorg/coq:${{ matrix.coq_image }} # See also: # https://github.com/coq-community/docker-coq-action#readme # https://github.com/erikmd/docker-coq-github-action-demo coq-record-update-0.3.3/.gitignore000066400000000000000000000005141450757146200170360ustar00rootroot00000000000000*.vo *.vos *.vok *.glob .*.aux # auto-generated .coqdeps.d .Makefile.coq.d Makefile.coq Makefile.coq.conf .coq-native/ native_compute_profile_*.data # generated timing files *.timing.diff *.v.after-timing *.v.before-timing *.v.timing time-of-build-after.log time-of-build-before.log time-of-build-both.log time-of-build-pretty.log coq-record-update-0.3.3/LICENSE000066400000000000000000000021031450757146200160470ustar00rootroot00000000000000The MIT License (MIT) Copyright 2020 Tej Chajed Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. coq-record-update-0.3.3/Makefile000066400000000000000000000032321450757146200165060ustar00rootroot00000000000000## this Makefile, as well as the test setup in Makefile.coq.local, is copied ## from std++ (https://gitlab.mpi-sws.org/iris/stdpp) # Forward most targets to Coq makefile (with some trick to make this phony) %: Makefile.coq phony +@$(MAKE) -f Makefile.coq $@ all: Makefile.coq +@$(MAKE) -f Makefile.coq all .PHONY: all clean: Makefile.coq +@$(MAKE) -f Makefile.coq clean find src tests \( -name "*.d" -o -name "*.vo" -o -name "*.vo[sk]" -o -name "*.aux" -o -name "*.cache" -o -name "*.glob" -o -name "*.vio" \) -print -delete || true rm -f Makefile.coq .lia.cache .PHONY: clean # Create Coq Makefile. Makefile.coq: _CoqProject Makefile "$(COQBIN)coq_makefile" -f _CoqProject -o Makefile.coq # Install build-dependencies build-dep/opam: opam Makefile @echo "# Creating build-dep package." @mkdir -p build-dep @sed build-dep/opam @fgrep builddep build-dep/opam >/dev/null || (echo "sed failed to fix the package name" && exit 1) # sanity check build-dep: build-dep/opam phony @# We want opam to not just instal the build-deps now, but to also keep satisfying these @# constraints. Otherwise, `opam upgrade` may well update some packages to versions @# that are incompatible with our build requirements. @# To achieve this, we create a fake opam package that has our build-dependencies as @# dependencies, but does not actually install anything itself. @echo "# Installing build-dep package." @opam install $(OPAMFLAGS) build-dep/ # Some files that do *not* need to be forwarded to Makefile.coq Makefile: ; _CoqProject: ; opam: ; # Phony wildcard targets phony: ; .PHONY: phony coq-record-update-0.3.3/Makefile.coq.local000066400000000000000000000035451450757146200203670ustar00rootroot00000000000000## this test configuration is copied ## from std++ (https://gitlab.mpi-sws.org/iris/stdpp) # use NO_TEST=1 to skip the tests NO_TEST:= # use MAKE_REF=1 to generate new reference files MAKE_REF:= # Run tests interleaved with main build. They have to be in the same target for this. real-all: $(if $(NO_TEST),,test) # the test suite TESTFILES:=$(shell find tests -name "*.v") NORMALIZER:=test-normalizer.sed test: $(TESTFILES:.v=.vo) .PHONY: test COQ_TEST=$(COQTOP) $(COQDEBUG) -batch -test-mode COQ_OLD=$(shell echo "$(COQ_VERSION)" | egrep "^8\.(8|9|10|11)\b" -q && echo 1) COQ_MINOR_VERSION:=$(shell echo "$(COQ_VERSION)" | egrep '^[0-9]+\.[0-9]+\b' -o) tests/.coqdeps.d: $(TESTFILES) $(SHOW)'COQDEP TESTFILES' $(HIDE)$(COQDEP) -dyndep var $(COQMF_COQLIBS_NOML) $^ $(redir_if_ok) -include tests/.coqdeps.d # Main test script (comments out-of-line because macOS otherwise barfs?!?) # - Determine reference file (`REF`). # - Print user-visible status line. # - Dump Coq output into a temporary file. # - Run `sed -i` on that file in a way that works on macOS. # - Either compare the result with the reference file, or move it over the reference file. # - Cleanup, and mark as done for make. $(TESTFILES:.v=.vo): %.vo: %.v $(if $(MAKE_REF),,%.ref) $(NORMALIZER) $(HIDE)if test -f $*".$(COQ_MINOR_VERSION).ref"; then \ REF=$*".$(COQ_MINOR_VERSION).ref"; \ else \ REF=$*".ref"; \ fi && \ echo "COQTEST$(if $(COQ_OLD), [no ref],$(if $(MAKE_REF), [make ref],)) $<$(if $(COQ_OLD),, (ref: $$REF))" && \ TMPFILE="$$(mktemp)" && \ $(TIMER) $(COQ_TEST) $(COQFLAGS) $(COQLIBS) -load-vernac-source $< > "$$TMPFILE" && \ sed -f $(NORMALIZER) "$$TMPFILE" > "$$TMPFILE".new && \ mv "$$TMPFILE".new "$$TMPFILE" && \ $(if $(COQ_OLD),true, \ $(if $(MAKE_REF),mv "$$TMPFILE" "$$REF",diff -u "$$REF" "$$TMPFILE") \ ) && \ rm -f "$$TMPFILE" && \ touch $@ coq-record-update-0.3.3/README.md000066400000000000000000000124101450757146200163230ustar00rootroot00000000000000# Coq record update library [![CI](https://github.com/tchajed/coq-record-update/actions/workflows/coq-action.yml/badge.svg)](https://github.com/tchajed/coq-record-update/actions/workflows/coq-action.yml) In a nutshell, this library automatically provides a generic way to update record fields. Here's a teaser example: ```coq From RecordUpdate Require Import RecordSet. Record X := mkX { A: nat; B: nat; C: bool; }. (* all you need to do is provide something like this, listing out the fields of your record: *) #[export] Instance etaX : Settable _ := settable! mkX . (* and now you can update fields! *) Definition setAB a b x := set B b (set A a x). (* you can also use a notation for the same thing: *) Import RecordSetNotations. Definition setAB' a b x := x <|A := a|> <|B := b|>. (* the notation also allows you to update nested fields: *) Record C := mkC { n : nat }. Record B := mkB { c : C }. Record A := mkA { b : B }. Instance etaC : Settable _ := settable! mkC. Instance etaB : Settable _ := settable! mkB. Instance etaA : Settable _ := settable! mkA. Definition setNested n' x := x <| b; c; n := n' |>. Definition incNested x := x <| b; c; n ::= S |>. ``` Coq has no record update syntax, nor does it create updaters for setting individual fields of a record. This small library automates creating such updaters. To use the library with a record, one must implement a typeclass `Settable` to provide the syntax for constructing a record from individual fields. This implementation lists out the record's constructor and every field accessor function. If you want to get rid of that boilerplate, you can, but it requires an OCaml plugin so it is provided separately; see [tchajed/coq-record-update-plugin](https://github.com/tchajed/coq-record-update-plugin). Once `Settable T` is implemented, Coq will be able to resolve the typeclass `Setter F` for all the fields `F` of `T`, so that a generic setter `set T A (F: T -> A) : forall {_:Setter F}, A -> T -> T` works. There is also a notation `x <| proj := v |>` for calling `set proj v x`. As a bonus, the `Setter F` typeclass includes some theorems showing the updater is correct. In addition, `Settable T` has a theorem showing that the fields are listed correctly. Together, these ensure that the library cannot be used incorrectly; for `Setter` this catches potential bugs in the library, while the property in `Settable` ensures that fields aren't listed out-of-order or duplicated. # Feedback and contributions If you have feedback or need some improvement to make this library useful to you, **please open an issue**. I do actively maintain it, though that has only required the occasional bug fix for a while. # Building and installing To build and install: ``` sh git clone https://github.com/tchajed/coq-record-update.git cd coq-record-update make # or make -j make install ``` # Wait, what? How does that work? I'm glad you asked! There are three tricks here: 1. First, we represent the fields of the record. The representation is actually just an identity function for the record, but it re-constructs the record from its fields; for example, it might look like `fun x => mkX (A x) (B x) (C x)`. I think of this expression as the record's eta expansion, since it deconstructs the record and then re-assembles it. 2. The second trick is that we can take this identity function and make a small tweak to it to turn it into an updater for a single field: if we replace a field with `f: R -> T` in the eta expansion (where `R` is the record type and `T` is the field type), instead of putting the field back as-is, we can substitute some update function. To actually implement this substitution without doing it by hand, we use the `pattern` tactic. This is easiest to illustrate with an example: `pattern field2 in (fun x => mkX (field1 x) (field2 x) (field3 x))` evaluates to `(fun f => (fun x => mkX (field1 x) (f x) (field3 x))) field2`. The first function is essentially the updater we want! We can now extract it with a simple Ltac pattern match. We do make one tweak which is rather than allowing the user to pass any function of the whole record, of type `R -> T`, we only allow a function of the current field value, of type `T -> T`. 3. The final piece of the puzzle is to get all of this Ltac to run. Here we (abuse) typeclasses, in two ways. You might notice that the `set` function in coq-record-update is just part of the class `Setter r field`. To resolve that class, we use a tactic rather than user-provided instances, and that tactic implements the `pattern` trick --- the tactic is easy to install because typeclass resolution is just an `auto`-like search using the `typeclass_instances` hint database, and we can sneak a `Hint Extern` into that database. That's the first typeclass trick. The second is used to look up the record eta expansion when resolving `Setter r field`. Here we have the user write a typeclass `Settable r` with the eta expansion and in Ltac we look up the eta expansion and then unfold it to look at the syntax, since the actual expression is relevant and not just its use as a function. In fact, you can implement `Settable` by providing the identity function and then setting won't work because the Ltac can't do anything with it. It's pretty cool what you can do with Coq typeclasses. coq-record-update-0.3.3/_CoqProject000066400000000000000000000002241450757146200171770ustar00rootroot00000000000000-Q src RecordUpdate -arg -w -arg +deprecated-instance-without-locality -arg -w -arg +undeclared-scope src/RecordSet.v src/RecordUpdate.v src/Lens.v coq-record-update-0.3.3/coq-record-update.opam000066400000000000000000000020271450757146200212430ustar00rootroot00000000000000# This file was generated from `meta.yml`, please do not edit manually. # Follow the instructions on https://github.com/coq-community/templates to regenerate. opam-version: "2.0" maintainer: "tchajed@gmail.com" version: "dev" homepage: "https://github.com/tchajed/coq-record-update" dev-repo: "git+https://github.com/tchajed/coq-record-update.git" bug-reports: "https://github.com/tchajed/coq-record-update/issues" license: "MIT" synopsis: "Generic support for updating record fields in Coq" description: """ While Coq provides projections for each field of a record, it has no convenient way to update a single field of a record. This library provides a generic way to update a field by name, where the user only has to implement a simple typeclass that lists out the record fields.""" build: [make "-j%{jobs}%"] install: [make "install"] depends: [ "coq" {(>= "8.14" & < "8.19~") | (= "dev")} ] tags: [ "category:Computer Science/Data Types and Data Structures" "keyword:record" "logpath:RecordUpdate" ] authors: [ "Tej Chajed" ] coq-record-update-0.3.3/dune-project000066400000000000000000000000651450757146200173710ustar00rootroot00000000000000(lang dune 2.5) (using coq 0.2) (name record-update) coq-record-update-0.3.3/meta.yml000066400000000000000000000021561450757146200165230ustar00rootroot00000000000000--- fullname: Record Update shortname: coq-record-update opam_name: coq-record-update organization: tchajed community: false travis: true coqdoc: false synopsis: >- Generic support for updating record fields in Coq description: |- While Coq provides projections for each field of a record, it has no convenient way to update a single field of a record. This library provides a generic way to update a field by name, where the user only has to implement a simple typeclass that lists out the record fields. authors: - name: Tej Chajed initial: true maintainers: - name: Tej Chajed nickname: tchajed opam-file-maintainer: tchajed@gmail.com opam-file-version: dev license: fullname: MIT License identifier: MIT supported_coq_versions: text: 8.8 or later opam: '{(>= "8.8" & < "8.16~") | (= "dev")}' tested_coq_opam_versions: - version: dev - version: '8.15' - version: '8.14' - version: '8.13' - version: '8.12' - version: '8.11' - version: '8.10' - version: '8.9' - version: '8.8' namespace: RecordUpdate keywords: - name: record categories: - name: Computer Science/Data Types and Data Structures --- coq-record-update-0.3.3/src/000077500000000000000000000000001450757146200156355ustar00rootroot00000000000000coq-record-update-0.3.3/src/Lens.v000066400000000000000000000015421450757146200167270ustar00rootroot00000000000000From RecordUpdate Require Import RecordSet. (* borrowed from https://github.com/bedrocksystems/coq-lens/blob/master/theories/Lens.v, with a function to define a field lens based on the [Setter] typeclass. This isn't as convenient to use, since we can't generate a lens per field without the kind of metaprograming in meta-coq or using a Coq plugin. *) Record Lens A1 A2 T1 T2 := mkLens { view : A1 -> T1; over : (T1 -> T2) -> (A1 -> A2); }. Arguments view {_ _ _ _} _ _ : assert. Arguments over {_ _ _ _} _ _ _ : assert. Definition field_lens {A T} (proj: A -> T) `{!Setter proj} : Lens A A T T := {| view := proj; over := set proj; |}. Definition lens_compose {A1 A2 T1 T2 C1 C2} (l1 : Lens A1 A2 T1 T2) (l2 : Lens T1 T2 C1 C2) := {| view x := view l2 (view l1 x); over f := over l1 (over l2 f); |}. coq-record-update-0.3.3/src/RecordSet.v000066400000000000000000000072151450757146200177230ustar00rootroot00000000000000Set Implicit Arguments. (** Settable is a way of accessing a constructor for a record of type T. The syntactic form of this definition is important: it must be an eta-expanded version of T's constructor, written generically over the field accessors of T. The best way to do this for a record X := mkX { A; B; C} is [settable! mkX ]. *) Class Settable T := { mkT: T -> T; mkT_ok: forall x, mkT x = x }. Arguments mkT T mk : clear implicits, rename. Local Ltac solve_mkT_ok := lazymatch goal with | [ |- forall x, _ = _ ] => first [ solve [ let x := fresh "x" in intro x; destruct x; reflexivity ] | fail 1 "incorrect settable! declaration (perhaps fields are out-of-order?)" ] end. (** settable! creates an instance of Settable from a constructor and list of fields. *) Notation "'settable!' mk < f1 ; .. ; fn >" := (Build_Settable (fun x => .. (mk (f1 x)) .. (fn x)) ltac:(solve_mkT_ok)) (at level 0, mk at level 10, f1, fn at level 9, only parsing). (** [setter] creates a setter based on an eta-expanded record constructor and a particular field projection proj *) Local Ltac setter etaT proj := lazymatch etaT with | context[proj] => idtac | _ => fail 1 proj "is not a field" end; let set := (match eval pattern proj in etaT with | ?setter _ => constr:(fun f => setter (fun r => f (proj r))) end) in exact set. (* Combining the above, [getSetter'] looks up the eta-expanded version of T with the Settable typeclass, and calls [setter] to create a setter. *) Local Ltac get_setter T proj := match constr:(mkT T _) with | mkT _ ?updateable => let updateable := (eval hnf in updateable) in match updateable with | {| mkT := ?mk |} => setter mk proj end end. (* Setter provides a way to change a field given by a projection function, along with correctness conditions that require the projected field and only the projected field is modified. *) Class Setter {R T} (proj: R -> T) := set : (T -> T) -> R -> R. Arguments set {R T} proj {Setter}. Class SetterWf {R T} (proj: R -> T) := { set_wf : Setter proj; set_get: forall v r, proj (set proj v r) = v (proj r); set_eq: forall f r, f (proj r) = proj r -> set proj f r = r; }. #[global] Existing Instance set_wf. Arguments set_wf {R T} proj {SetterWf}. Local Ltac SetterInstance_t := match goal with | |- @Setter ?T _ ?A => get_setter T A end. Local Ltac SetterWfInstance_t := match goal with | |- @SetterWf ?T _ ?A => unshelve notypeclasses refine (Build_SetterWf _ _ _); [ get_setter T A | let r := fresh in intros ? r; destruct r; reflexivity | let f := fresh in let r := fresh in intros f r; destruct r; cbv; congruence ] end. Global Hint Extern 1 (Setter _) => SetterInstance_t : typeclass_instances. Global Hint Extern 1 (SetterWf _) => SetterWfInstance_t : typeclass_instances. Module RecordSetNotations. Declare Scope record_set. Delimit Scope record_set with rs. Open Scope rs. Notation "x <| proj ::= f |>" := (set proj f x) (at level 12, f at next level, left associativity) : record_set. Notation "x <| proj := v |>" := (set proj (fun _ => v) x) (at level 12, left associativity) : record_set. Notation "x <| proj1 ; proj2 ; .. ; projn ::= f |>" := (set proj1 (set proj2 .. (set projn f) ..) x) (at level 12, f at next level, left associativity) : record_set. Notation "x <| proj1 ; proj2 ; .. ; projn := v |>" := (set proj1 (set proj2 .. (set projn (fun _ => v)) ..) x) (at level 12, left associativity) : record_set. End RecordSetNotations. coq-record-update-0.3.3/src/RecordUpdate.v000066400000000000000000000001071450757146200204030ustar00rootroot00000000000000From RecordUpdate Require Export RecordSet. Export RecordSetNotations. coq-record-update-0.3.3/src/dune000066400000000000000000000002351450757146200165130ustar00rootroot00000000000000(coq.theory (name RecordUpdate) (package coq-record-update) (synopsis "Generic support for updating record fields in Coq") (flags -w -undeclared-scope)) coq-record-update-0.3.3/test-normalizer.sed000066400000000000000000000005721450757146200207060ustar00rootroot00000000000000# adjust for https://github.com/coq/coq/pull/13656 s/subgoal/goal/g # remove these lines added in https://github.com/coq/coq/pull/14596 # (for backwards compatible output) /^Arguments/d # same PR adds additional blank lines /^$/d # locations in Fail added in https://github.com/coq/coq/pull/15174 /^File/d # extra space removed in https://github.com/coq/coq/pull/16130 s/= $/=/ coq-record-update-0.3.3/tests/000077500000000000000000000000001450757146200162105ustar00rootroot00000000000000coq-record-update-0.3.3/tests/LensTests.ref000066400000000000000000000000001450757146200206200ustar00rootroot00000000000000coq-record-update-0.3.3/tests/LensTests.v000066400000000000000000000006421450757146200203250ustar00rootroot00000000000000From RecordUpdate Require Import RecordUpdate Lens. Record X := mkX { A: nat; B: nat; C: bool; }. #[export] Instance etaX : Settable _ := settable! mkX . (* lenses require much more boilerplate than setters (if you want them to look like Haskell lenses) *) Definition _A := field_lens A. Definition _B := field_lens B. Definition _C := field_lens C. Definition set_A_to_3 (x:X) := over _A (fun _ => 3) x. coq-record-update-0.3.3/tests/ListNotationTests.ref000066400000000000000000000000001450757146200223460ustar00rootroot00000000000000coq-record-update-0.3.3/tests/ListNotationTests.v000066400000000000000000000011411450757146200220460ustar00rootroot00000000000000From RecordUpdate Require Import RecordSet. Require Import List. Import ListNotations. Import RecordSetNotations. Module GH4. Record foo := { a : bool ; b : bool }. Global Instance etaX_RtlExprs : Settable _ := settable! Build_foo . Definition bar := {| a := true ; b := true |}. Definition baz := bar<|a := false|>. End GH4. Definition l := [1; 2; 3]. Record foo := { a : list nat; b : list bool; }. #[export] Instance eta_foo : Settable _ := settable! Build_foo . Definition m_foo (x:foo) := x <| a := [1;2;3] |> <| b := [] |>. coq-record-update-0.3.3/tests/PrintingTests.ref000066400000000000000000000004441450757146200215250ustar00rootroot00000000000000setA = fun (x : X) (n : nat) => x <| B := n |> : X -> nat -> X updateA = fun (x : X) (n : nat) => x <| B ::= Nat.add n |> : X -> nat -> X setXB = fun n : Nested => n <| anX; B := 3 |> : Nested -> Nested updateXB = fun n : Nested => n <| anX; B ::= S |> : Nested -> Nested coq-record-update-0.3.3/tests/PrintingTests.v000066400000000000000000000010151450757146200212110ustar00rootroot00000000000000From RecordUpdate Require Import RecordUpdate. Record X := mkX { A: nat; B: nat; }. #[export] Instance etaX : Settable _ := settable! mkX . Definition setA (x:X) n := x <|B:=n|>. Print setA. Definition updateA (x:X) n := x <|B::=Nat.add n|>. Print updateA. Record Nested := mkNested { anX: X; aNat: nat; }. #[export] Instance etaNested : Settable _ := settable! mkNested . Definition setXB (n:Nested) := n <|anX; B:=3|>. Print setXB. Definition updateXB (n:Nested) := n <|anX; B::=S|>. Print updateXB. coq-record-update-0.3.3/tests/ReadmeExampleTests.ref000066400000000000000000000000001450757146200224300ustar00rootroot00000000000000coq-record-update-0.3.3/tests/ReadmeExampleTests.v000066400000000000000000000007301450757146200221330ustar00rootroot00000000000000From RecordUpdate Require Import RecordSet. Record X := mkX { A: nat; B: nat; C: bool; }. (* all you need to do is provide something like this, listing out the fields of your record: *) #[export] Instance etaX : Settable _ := settable! mkX . (* and now you can update fields! *) Definition setAB a b x := set B b (set A a x). (* you can also use a notation for the same thing: *) Import RecordSetNotations. Definition setAB' a b x := x <|A := a|> <|B := b|>. coq-record-update-0.3.3/tests/RecordSetTests.ref000066400000000000000000000000001450757146200216110ustar00rootroot00000000000000coq-record-update-0.3.3/tests/RecordSetTests.v000066400000000000000000000070231450757146200213160ustar00rootroot00000000000000From RecordUpdate Require Import RecordSet. Set Implicit Arguments. Module SimpleExample. Record X := mkX { A: nat; B: nat; C: unit }. #[export] Instance etaX : Settable _ := settable! mkX . Import RecordSetNotations. Definition setAB a b x := x <|A := a|> <|B := b|>. Definition updateAB a b x := x <|A ::= plus a|> <|B ::= minus b|>. End SimpleExample. Module IndexedType. Record X {T} := mkX { A: T; B: T; C: unit }. Arguments X T : clear implicits. #[export] Instance etaX T: Settable (X T) := settable! (mkX (T:=T)) < A; B; C>. Import RecordSetNotations. Definition setAB T a b (x: X T) := x <|A := a|> <|B := b|>. End IndexedType. Module DependentExample. Record X := mkX { T: Type; A: T; B: nat }. #[export] Instance etaX : Settable X := settable! mkX . Import RecordSetNotations. Definition setB b x := x <|B := b|>. End DependentExample. Module WellFormedExample. Record X := mkX { A: nat; B: nat; C: unit }. #[export] Instance etaX : Settable _ := settable! mkX . Definition setAB a b x := set A (fun _ => a) (set B (fun _ => b) x). (* Resolving an instance for SetterWf proves some correctness properties of the setter. You can also require constructing this instance by accessing the setter through set_wf. *) #[export] Instance set_A : SetterWf A. Proof. apply _. Qed. Definition setAB_wf a b x := set_wf A (fun _ => a) (set_wf B (fun _ => b) x). End WellFormedExample. Module DependentWfExample. Record X := mkX { T: Type; A: T; B: nat }. #[export] Instance etaX : Settable X := settable! mkX . #[export] Instance set_A : SetterWf B. Proof. apply _. Qed. End DependentWfExample. Module NestedExample. Record C := mkC { n : nat }. Record B := mkB { c : C }. Record A := mkA { b : B }. #[export] Instance etaC : Settable _ := settable! mkC. #[export] Instance etaB : Settable _ := settable! mkB. #[export] Instance etaA : Settable _ := settable! mkA. Import RecordSetNotations. Definition setNested n' x := x <| b; c; n := n' |>. End NestedExample. Module TypeParameterExample. Record X T := mkX { a: nat; b: T; c: T * T; }. Arguments a {T}. Arguments b {T}. Arguments c {T}. #[export] Instance etaX T : Settable _ := settable! (@mkX T) . Import RecordSetNotations. Definition set_a (x:X unit) := x <| a := 3 |>. Definition set_b (x:X unit) := x <| b := tt |>. Definition set_b' {T} (x:X T) (v:T) := x <| b := v |>. Definition set_c {T} (x:X T) (v:T) := x <| c := (v,v) |>. End TypeParameterExample. Module TypeParameterLimitation. Record X T := mkX { a: nat; b: T; }. Arguments a {T}. Arguments b {T}. #[export] Instance etaX T : Settable _ := settable! (@mkX T) . Import RecordSetNotations. Definition set_a (x:X unit) := x <| a := 3 |>. Definition set_b {T} (x:X T) (v:T) := x <| b := v |>. (* unsupported by RecordUpdate: the pattern trick could do this in principle, but the type of [set] in the [Setter] typeclass is too restrictive to allow the change in X's type. We could give [Setter] a broader type (where the type of the record can change), but then I'd worry about type inference being underconstrained in the common case. *) Definition strong_update_to_b {T1 T2} (x: X T1) (v: T2) : X T2 := mkX (a x) v. End TypeParameterLimitation. coq-record-update-0.3.3/tests/RegressionTests.ref000066400000000000000000000007001450757146200220460ustar00rootroot00000000000000The command has indeed failed with message: The following term contains unresolved implicit arguments: (fun (r : X) (a : nat) => r <| getA := a |>) More precisely: - ?Setter: Cannot infer the implicit parameter Setter of set whose type is "Setter getA" (no type class instance found) in environment: r : X a : nat test = fun (s : ThreadState) (newRegs : Registers) => s <| Regs := newRegs |> : ThreadState -> Registers -> ThreadState coq-record-update-0.3.3/tests/RegressionTests.v000066400000000000000000000022561450757146200215470ustar00rootroot00000000000000From RecordUpdate Require Import RecordUpdate. Module GH2. Record X := mkX { A: nat;}. #[export] Instance etaX : Settable _ := settable! mkX . (* name r should not prevent finding a Setter A instance *) Definition setA (r : nat) x := x <|A := 32|>. End GH2. Module GH5. Record X := mkX { A: nat; }. #[export] Instance etaX : Settable _ := settable! mkX . Definition getA (x:X) := let 'mkX a := x in a. (* should not succeed, getA is not a projection *) Fail Definition setA (r: X) (a: nat) := set getA (fun _ => a) r. End GH5. Module GH10. Record X := mkX { A: nat; B: nat; x: bool; }. #[export] Instance etaX : Settable _ := settable! mkX . End GH10. Module GH13. Axiom Registers: Type. Axiom word: Type. Record ThreadState := mkThreadState { Regs: Registers; Pc: word; }. #[export] Instance ThreadStateSettable : Settable ThreadState := settable! mkThreadState . Definition test(s: ThreadState)(newRegs: Registers): ThreadState := s <| Regs := newRegs |>. (* should be printed with set notation, not update notation *) Print test. End GH13. coq-record-update-0.3.3/tests/SimpleRecordUpdate.ref000066400000000000000000000001731450757146200224420ustar00rootroot00000000000000(fun x : X => {| A := A x; B := B x; C := C x |}) (fun (f : nat -> nat) (x : X) => {| A := A x; B := f (B x); C := C x |}) coq-record-update-0.3.3/tests/SimpleRecordUpdate.v000066400000000000000000000142431450757146200221360ustar00rootroot00000000000000(*| ============================ How coq-record-update works ============================ A detailed explanation of how coq-record-update is implemented. This is a re-implementation that omits the safety checks in the actual implementation, so that the basic story is as clear as possible. This explanation interleaves a simplified re-implementation of the library with demo modules that demonstrate features as they are added. These demo modules each import the previous, but we hide that command to avoid cluttering the output. First, our basic goal is to implement the following typeclass for each record type `R` and projection function `proj`. The implicit arguments are set up so that setting a field `A` is as simple as `set A (fun a => a + 1) x`, which will increment the field `A` in a record `x`. |*) Class Setter {R T:Type} (proj: R -> T) := set : (T -> T) -> (R -> R). Arguments set {R T} proj {_} _ _ : assert. Global Hint Mode Setter - - + : typeclass_instances. Module demo1. (*| My favorite running example, a simple record with three fields. |*) Record X := mkX { A: nat; B: nat; C: bool; }. (*| With just the above definition, we would still need to implement the typeclass for every field of every record. Here's the particular form of implementation that we'll automate with this library. |*) #[export] Instance setA : Setter A := fun (f:nat -> nat) (x:X) => mkX (f (A x)) (B x) (C x). #[export] Instance setB : Setter B := fun (f:nat -> nat) (x:X) => mkX (A x) (f (B x)) (C x). #[export] Instance setC : Setter C := fun (f:bool -> bool) (x:X) => mkX (A x) (B x) (f (C x)). End demo1. (*| The basis for the automation will be to extract the common parts of the above, an "eta expansion" that doesn't set any fields but reconstructs a record from all of its fields. The user will provide the eta expansion by implementing the `Settable` typeclass. |*) Class Settable (R: Type) := mkRecord : R -> R. Arguments mkRecord R _ _ : assert, clear implicits. Global Hint Mode Settable + : typeclass_instances. Module demo2. Import demo1. (* .none *) (*| Here's how we intend to implement `Settable`. This is equal to `fun x => x`, but we won't actually call `mkRecord`, instead we'll look up the implementation of `Settable` and actually look at the definition (rather than using the instances opaquely). |*) #[export] Instance etaX : Settable X := fun x => mkX (A x) (B x) (C x). End demo2. (*| Looking up a typeclass instance is pretty simple if you think about it: we just typecheck `_ : Settable R`, which will trigger typeclass resolution to fill in the underscore! |*) Ltac get_eta R := let eta := eval hnf in (_ : Settable R) in eta. (*| Given an eta expansion (recall they look like `fun x => mkX (A x) (B x) (C x)`), we want to substitute an update function in the place of some projection function `proj`. The way we can do that is with the `pattern` tactic. Let's say we call `make_setter eta B`, where `eta` is the above eta expansion. `setter_r` factors it into `(fun B_f => mkX (A x) (B_f x) (C x)) B`. This is almost what we want, except the `set` function doesn't allow any function of the whole record, only of `B x`, so we actually use `fun r => f (B r)` as the replacement for `B` (note that this is just `f ∘ B`, expanded out). |*) Ltac make_setter eta proj := let setter_r := (eval pattern proj in eta) in lazymatch setter_r with | ?set_f _ => let setter := constr:(fun f => set_f (fun r => f (proj r))) in (* we can clean up the actual setter term by beta-reducing it *) let setter := (eval cbn beta in setter) in setter end. Module demo3. Import demo1 demo2. (* .none *) (*| Let's see what the above tactics do before we tie everything together into a nice user interface. Recall that we've already implemented `Settable X`, so `get_eta X` can look it up. |*) Goal True. (* .in .messages *) let eta := get_eta X in idtac eta. (* .in .messages .unfold *) let eta := get_eta X in let setter_B := make_setter eta B in idtac setter_B. (* .in .messages .unfold *) Abort. End demo3. (*| Finally, we want to use `make_setter` without any fancy syntax. The way we do that is to implement the `Setter` typeclass using Ltac, rather than the usual mechanism of adding definitions as instances. Instance resolution turns out to just be a (slightly modified) `eauto` search using the `typeclass_instances` hint database, so we can register a `Hint Extern` to use `make_setter` to resolve `Setter`. First we package up `get_eta` and `make_setter` into a single tactic that will solve goals of the form `Setter proj`, as long as `Settable` is implemented for the relevant record type. |*) Ltac solve_setter R proj := let eta := get_eta R in let setter := make_setter eta proj in exact setter. (*| Now we add `solve_setter` as a way to prove `Setter` during typeclass resolution. |*) Global Hint Extern 1 (@Setter ?R _ ?proj) => solve_setter R proj : typeclass_instances. (*| To make the library usable, we provide a notation that builds the eta expansion from a list of record fields, which is much easier to type than the actual eta expansion. |*) Notation "'settable!' mk < f1 ; .. ; fn >" := (fun x => .. (mk (f1 x)) .. (fn x)) (at level 0, mk at level 10, f1, fn at level 9, only parsing). Module demo4. Import demo1. (* .none *) (*| Before we had to write out the expansion of X carefully; now we can just list out the constructor and fields: |*) #[export] Instance etaX : Settable _ := settable! mkX . End demo4. (*| We also provide notations for the `set` function that make multiple updates, and updates to constants easier to read and write. |*) Notation "x <| proj ::= f |>" := (set proj f x) (at level 12, f at next level, left associativity, format "x <| proj ::= f |>"). Notation "x <| proj := v |>" := (set proj (fun _ => v) x) (at level 12, left associativity, format "x <| proj := v |>"). Module test. Record X := mkX { A: nat; B: nat; C: unit }. #[export] Instance eta : Settable X := settable! mkX . Definition setAB a b x := x <|A := a|> <|B := b|>. Definition updateAB a b x := x <|A ::= plus a|> <|B ::= minus b|>. End test. coq-record-update-0.3.3/tests/coqpl_2021.ref000066400000000000000000000030201450757146200204630ustar00rootroot000000000000001 goal f : nat -> nat x : X ============================ {| A := A x; B := f (B x); C := C x |} = {| A := A x; B := f (B x); C := C x |} 1 goal x : X ============================ 3 = 3 The command has indeed failed with message: The following term contains unresolved implicit arguments: (set Nat.add) More precisely: - ?Setter: Cannot infer the implicit parameter Setter of set whose type is "Setter Nat.add" (no type class instance found). The command has indeed failed with message: The following term contains unresolved implicit arguments: (set get_A) More precisely: - ?Setter: Cannot infer the implicit parameter Setter of set whose type is "Setter get_A" (no type class instance found). The command has indeed failed with message: In environment x : several_nats The term "Build_several_nats (nat1 x) (nat3 x)" has type "nat -> several_nats" while it is expected to have type "several_nats". The command has indeed failed with message: Tactic failure: incorrect settable! declaration (perhaps fields are out-of-order?). The command has indeed failed with message: Tactic failure: incorrect settable! declaration (perhaps fields are out-of-order?). The command has indeed failed with message: The following term contains unresolved implicit arguments: (fun (f : nat -> nat) (x : several_nats) => x <| nat1 ::= f |>) More precisely: - ?Setter: Cannot infer the implicit parameter Setter of set whose type is "Setter nat1" (no type class instance found) in environment: f : nat -> nat x : several_nats coq-record-update-0.3.3/tests/coqpl_2021.v000066400000000000000000000045421450757146200201660ustar00rootroot00000000000000From RecordUpdate Require Import RecordUpdate. Record X := mkX { A: nat; B: nat; C: bool }. (* you can omit the X; it's there to clarify the Settable class for the paper *) #[export] Instance: Settable X := settable! mkX . Definition add3_to_B x := set B (plus 3) x. Definition setB_to_3 x := set B (fun _ => 3) x. Definition setB_to_3_notation x := x <|B:=3|>. #[export] Instance set_B : Setter B := _. Theorem set_B_convertible_to f x : set_B f x = let a := x.(A) in let b' := f x.(B) in let c := x.(C) in mkX a b' c. Proof. reflexivity. Qed. Theorem set_B_is f x : set_B f x = mkX x.(A) (f x.(B)) x.(C). Proof. unfold set_B. Show. match goal with | |- ?x = ?x => reflexivity | _ => fail 1 "not an exact match" end. Qed. Theorem simpl_behavior x : (set A (fun _ => 2) (set B (fun _ => 3) x)).(B) = 3. Proof. simpl. Show. match goal with | |- ?x = ?x => reflexivity | _ => fail 1 "did not simplify correctly" end. Qed. Fail Definition error_not_field := set plus. Definition get_A x := A x. (* the Ltac produces a better error message, but typeclass resolution swallows up the error *) Fail Definition error_not_proj := set get_A. Record several_nats := { nat1: nat; nat2: nat; nat3: nat; }. Definition add2 (x:several_nats) := nat1 x + nat2 x. Definition nat1_synonym x := nat1 x. (* fails with a typechecking error, because the constructed identity function doesn't typecheck (we could do better by using tactics-in-terms to fail with a custom error message) *) Fail #[export] Instance: Settable _ := settable! Build_several_nats . (* fails because fields are out-of-order *) Fail #[export] Instance: Settable _ := settable! Build_several_nats . (* one of these just isn't a field, so the result isn't an identity function *) Fail #[export] Instance: Settable _ := settable! Build_several_nats . (* this isn't intentionally supported, but now we can only set nat1 via its synonym (actually, the only thing special about nat1 vs nat1_synonym is that Coq auto-generated nat1) *) #[export] Instance: Settable _ := settable! Build_several_nats . (* this no longer works because the Settable several_nats doesn't say anything about nat1 *) Fail Definition set_nat1 f (x: several_nats) := set nat1 f x. Definition set_nat1 f (x: several_nats) := set nat1_synonym f x.