Encoding data as lists of numbers
Lax185574.Encoding · concepts/Lax185574/Encoding.lean · lax-185574
No public endorsements yet.
Loading review…
Sign in with ORCIDNatural Language Statement
Definition
A random access machine reads its input as a list of natural numbers and writes its output as one. An object is handed to an algorithm through an encoding, a map from its type to lists of numbers. The encodings in this submission follow one rule, with a few exceptions.
- A natural number is the one-element list containing it.
- A value of an inductive type is a constructor applied to some arguments. Its encoding is the number of the constructor (counting from 0 in the order of declaration), followed by the encodings of the arguments in order. If the type has only one constructor, the number is left out.
Arguments that are proofs carry no information and are left out. So are the implicit arguments that the type of the value determines, such as the number of free variables of a formula.
For example, the pair is encoded as , since is the second constructor of . An element of is a natural number together with a proof that it is less than , so it is encoded as .
The exceptions are listed in . The main ones are lists and finite sets. A list is encoded by its length, followed by the encodings of its entries, so the list is encoded as . A finite set is encoded by the list of the encodings of its elements, in increasing order.
Each such encoding is a prefix code. An algorithm that knows the type can read the encoding from the front, one constructor or one length at a time, without knowing where it ends. The encoding has length linear in the size of the value, and every number in it is a number of the value, the number of a constructor or the length of a list. Some of the other exceptions write further numbers, such as d + 1 for a finite value d of ℕ∞.
Concept map
Lean source view on GitHub
| 1 | import Lean.Elab.Deriving.Basic |
| 2 | import Lean.Elab.Deriving.Util |
| 3 | import Mathlib.Data.Nat.Notation |
| 4 | |
| 5 | /-! |
| 6 | --- |
| 7 | title: Encoding data as lists of numbers |
| 8 | type: definition |
| 9 | --- |
| 10 | A random access machine reads its input as a list of natural numbers and |
| 11 | writes its output as one. An object is handed to an algorithm through an |
| 12 | *encoding*, a map from its type to lists of numbers. The encodings in |
| 13 | this submission follow one rule, with a few exceptions. |
| 14 | |
| 15 | * A natural number is the one-element list containing it. |
| 16 | * A value of an inductive type is a constructor applied to some |
| 17 | arguments. Its encoding is the number of the constructor (counting |
| 18 | from 0 in the order of declaration), followed by the encodings of the |
| 19 | arguments in order. If the type has only one constructor, the number is |
| 20 | left out. |
| 21 | |
| 22 | Arguments that are proofs carry no information and are left out. So are |
| 23 | the implicit arguments that the type of the value determines, such as |
| 24 | the number of free variables of a formula. |
| 25 | |
| 26 | For example, the pair `(3, true)` is encoded as `3, 1`, since `true` is |
| 27 | the second constructor of `Bool`. An element `i` of `Fin n` is a natural |
| 28 | number together with a proof that it is less than `n`, so it is encoded |
| 29 | as `i`. |
| 30 | |
| 31 | The exceptions are listed in `Lax185574.Encodings`. The main ones are |
| 32 | lists and finite sets. A list is encoded by its length, followed by the |
| 33 | encodings of its entries, so the list `[7, 8]` is encoded as `2, 7, 8`. |
| 34 | A finite set is encoded by the list of the encodings of its elements, in |
| 35 | increasing order. |
| 36 | |
| 37 | Each such encoding is a prefix code. An algorithm that knows the type |
| 38 | can read the encoding from the front, one constructor or one length at a |
| 39 | time, without knowing where it ends. The encoding has length linear in |
| 40 | the size of the value, and every number in it is a number of the value, |
| 41 | the number of a constructor or the length of a list. Some of the other |
| 42 | exceptions write further numbers, such as d + 1 for a finite value d of |
| 43 | ℕ∞. |
| 44 | |
| 45 | # Formalization notes |
| 46 | |
| 47 | The rule is the class `Encode` together with `deriving Encode`, a |
| 48 | deriving handler that writes the encoding function of an inductive type |
| 49 | from its declaration, by structural recursion. The handler is the |
| 50 | counterpart of Lean's own `deriving Hashable`, with the constructor number |
| 51 | and the encodings of the arguments in place of hashes. It supports |
| 52 | recursive types and indexed families. It rejects mutual and nested |
| 53 | inductive types, and it rejects an implicit argument that the type of the |
| 54 | constructor does not determine, since leaving such an argument out could |
| 55 | lose information. Natural numbers are the only base case. They are an |
| 56 | inductive type too, but their derived encoding would be unary, so their |
| 57 | instance is written by hand. |
| 58 | |
| 59 | Lists are an inductive type as well. Their derived encoding would mark |
| 60 | every entry with the number of `cons`. The length in front is the usual |
| 61 | convention, and it lets an algorithm skip a list, or find its end, |
| 62 | without reading its entries. |
| 63 | |
| 64 | Types whose values are not built from constructors, such as graphs given |
| 65 | by an adjacency relation, are encoded through an explicit representation |
| 66 | by an encodable type, as the graphs of `Lax185574.Encodings`. |
| 67 | |
| 68 | The size of a value, in bounds on running times and on outputs, is the |
| 69 | length of its encoding, `wordLength`. |
| 70 | |
| 71 | No decoding function is part of the definition. A statement about an |
| 72 | algorithm only fixes what it reads and what it must write, and these are |
| 73 | determined by the encoding alone. |
| 74 | -/ |
| 75 | |
| 76 | namespace Lax185574.Encoding |
| 77 | |
| 78 | universe u |
| 79 | |
| 80 | /-- An encoding of the values of `α` as lists of natural numbers. -/ |
| 81 | class Encode (α : Type u) where |
| 82 | /-- The list of numbers encoding a value. -/ |
| 83 | encode : α → List ℕ |
| 84 | |
| 85 | export Encode (encode) |
| 86 | |
| 87 | /-- The size `‖a‖` of a value, as used in running-time bounds: the |
| 88 | length of its encoding. -/ |
| 89 | def wordLength {α : Type u} [Encode α] (a : α) : ℕ := (encode a).length |
| 90 | |
| 91 | /-- A natural number is encoded as itself. -/ |
| 92 | instance : Encode ℕ where |
| 93 | encode n := [n] |
| 94 | |
| 95 | section Deriving |
| 96 | |
| 97 | open Lean Elab Command Meta Term Deriving |
| 98 | open Lean.Parser.Term |
| 99 | |
| 100 | /-- The match expression of the derived encoding function: one |
| 101 | alternative per constructor, whose right-hand side is the constructor |
| 102 | number (if there are several constructors) followed by the encodings of |
| 103 | the explicit arguments that are not proofs. Recursive arguments are |
| 104 | encoded by a recursive call. -/ |
| 105 | def mkEncodeMatch (ctx : Deriving.Context) (header : Header) (indVal : InductiveVal) : |
| 106 | TermElabM Term := do |
| 107 | let discrs ← mkDiscrs header indVal |
| 108 | if indVal.ctors.isEmpty then |
| 109 | return ← `(nomatch $(mkIdent header.targetNames[0]!):ident) |
| 110 | let mut alts : Array (TSyntax ``matchAlt) := #[] |
| 111 | for ctorName in indVal.ctors, ctorIdx in [0:indVal.ctors.length] do |
| 112 | let ctorInfo ← getConstInfoCtor ctorName |
| 113 | let alt : TSyntax ``matchAlt ← forallTelescopeReducing ctorInfo.type fun xs resultType => do |
| 114 | let mut patterns : Array Term := #[] |
| 115 | for _ in [0:indVal.numIndices] do |
| 116 | patterns := patterns.push (← `(_)) |
| 117 | let mut ctorArgs : Array Term := #[] |
| 118 | for _ in [0:indVal.numParams] do |
| 119 | ctorArgs := ctorArgs.push (← `(_)) |
| 120 | let mut parts : Array Term := #[] |
| 121 | if indVal.ctors.length > 1 then |
| 122 | parts := parts.push (← `([$(quote ctorIdx)])) |
| 123 | for i in [0:ctorInfo.numFields] do |
| 124 | let x := xs[indVal.numParams + i]! |
| 125 | let a := mkIdent (← mkFreshUserName `a) |
| 126 | ctorArgs := ctorArgs.push a |
| 127 | let decl ← x.fvarId!.getDecl |
| 128 | let xTy ← whnf decl.type |
| 129 | if ← isProp decl.type then |
| 130 | continue |
| 131 | unless decl.binderInfo.isExplicit do |
| 132 | unless resultType.containsFVar x.fvarId! do |
| 133 | throwError "deriving Encode: the implicit argument {decl.userName} of \ |
| 134 | {ctorName} is not determined by the type of the constructor" |
| 135 | continue |
| 136 | match xTy.getAppFn with |
| 137 | | .const declName .. => |
| 138 | if declName == indVal.name then |
| 139 | parts := parts.push (← `($(mkIdent ctx.auxFunNames[0]!) $a:ident)) |
| 140 | else |
| 141 | parts := parts.push (← `(Encode.encode $a:ident)) |
| 142 | | _ => parts := parts.push (← `(Encode.encode $a:ident)) |
| 143 | patterns := patterns.push (← `(@$(mkIdent ctorName):ident $ctorArgs:term*)) |
| 144 | let rhs : Term ← if parts.isEmpty then `(([] : List Nat)) |
| 145 | else parts[1:].foldlM (fun acc p => `($acc ++ $p)) parts[0]! |
| 146 | `(matchAltExpr| | $[$patterns:term],* => $rhs:term) |
| 147 | alts := alts.push alt |
| 148 | `(match $[$discrs],* with $alts:matchAlt*) |
| 149 | |
| 150 | /-- The commands defining the encoding function of the inductive type |
| 151 | `declName` and its `Encode` instance. -/ |
| 152 | def mkEncodeInstanceCmds (declName : Name) : TermElabM (Array Syntax) := do |
| 153 | let ctx ← mkContext ``Encode "encode" declName |
| 154 | if ctx.usePartial then |
| 155 | throwError "deriving Encode: mutual and nested inductive types are not supported" |
| 156 | let indVal := ctx.typeInfos[0]! |
| 157 | let header ← mkHeader ``Encode 1 indVal |
| 158 | let body ← mkEncodeMatch ctx header indVal |
| 159 | let fn ← `(def $(mkIdent ctx.auxFunNames[0]!):ident $(header.binders):bracketedBinder* : |
| 160 | List Nat := $body:term) |
| 161 | return #[fn] ++ (← mkInstanceCmds ctx ``Encode #[declName]) |
| 162 | |
| 163 | /-- The deriving handler for `Encode`. -/ |
| 164 | def mkEncodeHandler (declNames : Array Name) : CommandElabM Bool := do |
| 165 | unless ← declNames.allM isInductive do |
| 166 | return false |
| 167 | for declName in declNames do |
| 168 | let cmds ← liftTermElabM <| mkEncodeInstanceCmds declName |
| 169 | cmds.forM elabCommand |
| 170 | return true |
| 171 | |
| 172 | initialize registerDerivingHandler ``Encode mkEncodeHandler |
| 173 | |
| 174 | end Deriving |
| 175 | |
| 176 | end Lax185574.Encoding |
| 177 |
Formalization notes
The rule is the class together with , a deriving handler that writes the encoding function of an inductive type from its declaration, by structural recursion. The handler is the counterpart of Lean's own , with the constructor number and the encodings of the arguments in place of hashes. It supports recursive types and indexed families. It rejects mutual and nested inductive types, and it rejects an implicit argument that the type of the constructor does not determine, since leaving such an argument out could lose information. Natural numbers are the only base case. They are an inductive type too, but their derived encoding would be unary, so their instance is written by hand.
Lists are an inductive type as well. Their derived encoding would mark every entry with the number of . The length in front is the usual convention, and it lets an algorithm skip a list, or find its end, without reading its entries.
Types whose values are not built from constructors, such as graphs given by an adjacency relation, are encoded through an explicit representation by an encodable type, as the graphs of .
The size of a value, in bounds on running times and on outputs, is the length of its encoding, .
No decoding function is part of the definition. A statement about an algorithm only fixes what it reads and what it must write, and these are determined by the encoding alone.
Discussion
Ask a question or add context. Endorsements and structured flags are kept in the review panel above.
0 comments