Introduction

It’s just syntax, said once a trained mathematician, and this has a grain of truth. Different higher-order theorem provers do not differ too much in their definitional mechanisms, only in concrete syntax. Still, if you want to master them, syntax is essential.

Here I would like to gather the most important surface syntax of the HOL4 theorem prover.

A Script file

HOL4 proof scripts are stored in XXXXScript.sml files. This file shows the basic Syntax:

open HolKernel bossLib

val _ = new_theory "trivial"

Theorem reflexivity:
  x = x
Proof
  simp []
QED

val _ = export_theory();

With open we can refer to existing HOL4 libraries. For an extensive documentation of libraries, see the HOL Reference Page.

There are two main modes of using HOL4, the interactive mode that is helped with the hol-vim, emacs hol mode or the Visual Studio Code extension, and the batch mode, that is, running your script files through Holmake. In the interactive mode you should load these libraries by hand, in batch mode it is done automatically. To do so interactively, one must use the keyword load, but the user interfaces derive the component list from the open declaration. The interactive mode is a read-eval-print-loop of the underlying SML compiler. The user interfaces are using this interactive loop.

The command new_theory defines the name of the theory and starts the session. The file name’s prefix should coincide with the string given here, otherwise you get an error message. So here my file is trivialScript.sml.

The file ends with export_theory, which closes the session started with new_theory. Running Holmake will create a file that can be used from other script files, the same way as we imported system libraries with “open”.

Between these there is a theorem and its proof in a Theorem-Proof-QED block. These keywords should start at the beginning of the line. We should give a name to the theorem, afterwards we can refer to it by this name. After theorem, we can give the goal we would like to prove, and after Proof, one can write the tactic that is meant to prove the goal.

Definitions

The following user-friendly syntax elements are mentioned in the Description index as special syntactic forms for scripts .

Datatypes

A datatype is an algebraic type, familiar from functional languages. For a binary tree, we can write

Datatype `bintree = Leaf | Node bintree 'a bintree`

in HOL4, which is the equivalent of

datatype 'a bintree = Leaf | Node of 'a bintree * 'a * 'a bintree

in Standard ML. As we can see, the HOL4 definition is more terse. Be aware of the use of backticks for the Datatype definition in HOL4. An alternative HOL4 syntax is

Datatype: btree = LEAF | NODE btree 'a btree
End

As before, the Datatype and `End´ keywords should start their lines.

Recursive Functions

Defining a recursive function has this syntax:

Definition fibonacci_def:
  (fib 0 = 0) ∧
  (fib 1 = 1) ∧
  (fib n = fib (n-2) + fib (n-1))
End

As usual, the order of patterns matter, the first has precedence over the others, and so on. Also usual is the position of Definition and End at the start of the line.

When the system cannot prove automatically that the function definition always terminates, so the function is total, we need to submit a termination proof. Its syntax involves the keyword Termination at the beginning of a line following the function definition:

Definition qsort_def:
  (qsort ord [] = []) /\
  (qsort ord (h::t) =
    qsort ord (FILTER (\x. ord x h) t) ++ [h] ++
    qsort ord (FILTER (\x. ~(ord x h)) t))
Termination
  WF_REL_TAC `measure (LENGTH o SND)` THEN cheat
End

Inductive Relations

For defining inductive relations the syntax is:

Inductive Even:
  even 0 ∧
  ∀n. even n ⇒ even (n+2)
End

Records

We can define records as

Datatype: person = <| employed : bool ; age : num ; name : string |>
End

and for a value let’s have

<| age := 21; employed := F; name := "Layabout" |>

For field access, person_age is the syntax. For field update, bob with employed updated_by f is written, where f is the function that is used for a new value of the field employed.

Case expressions

Case expressions are for pattern matching:

case n of
  0 => "none"
| 1 => "one"
| 2 => "two"
| _ => "many"

Type abbreviations

Compound types can be abbreviated with the new syntax:

Type set = “:’a -> bool”

This is an input-only syntax if used this way. However, if you use the attribute pp, which is for pretty-printing, then HOL4 will print the type abbreviation in its output properly:

Type set[pp] = “:’a -> bool”

Triviality

For locally important theorems, we can use an attribute local for Theorem definitions:

Theorem fermat[local]:

These are not exported, only used locally. The user-friendly syntax for this is

Triviality fermat:

with the usual Proof and QED.

Overloading

For overloading, there is the syntax

Overload "%%" = “CEILING_MOD”;

Types

HOL4 gives us its type grammar with the command type_grammar:

> type_grammar();
val it =
   Rules:
     (50)   TY  ::=  TY -> TY [fun] (R-associative)
     (60)   TY  ::=  TY + TY [sum] (R-associative)
     (70)   TY  ::=  TY # TY [prod] (R-associative)
            TY  ::=  bool | (TY, TY) fun | ind | TY itself | TY list | num |
                     one | TY option | (TY, TY) prod | TY recspace | TY set |
                     (TY, TY) sum | unit
            TY  ::=  TY[TY] (array type)
   Type abbreviations:
     bool             = min$bool
     (α, β) fun       = (α, β) min$fun
     ind              = min$ind
     α itself         = α bool$itself
     α list           = α list$list
     num              = num$num
     one              = one$one                [not printed]
     α option         = α option$option
     (α, β) prod      = (α, β) pair$prod
     α recspace       = α ind_type$recspace
     α set            = (α, min$bool) min$fun  [not printed]
     (α, β) sum       = (α, β) sum$sum
     unit             = one$one               : type_grammar.grammar

We start by covering the non-standard notation. At the beginning we see the notation for function, sum and record types, with record type being the somewhat unusual hash mark, not the standard star in SML. All these are right-associative (i.e. A op B op C means A op (B op C)). The ind type is the one for individuals, as we know it in formal logic. For any type, α itself is a one-element type whose element represents the type. Its role is technical in building more complex types like the fixed width word type. The type num is the type of natural numbers. The type one is the same as the unit type with the only element (). Type α recspace is again a technical type used in the type definition package, holding finitely branching recursive types built from subsets of α. For an expert description see an old mail of John Harrison.

Type variables

For input syntax, type variables are possibly a prime followed by an alphanumeric string: 'a 'foo 'A11. A prime followed by a single lower case latin letter is translated to a lowercase greek letter in output. Lambda is omitted, because of its special status in higher order logic, namely, the lambda operator. The input syntax 'l is translated to μ, and so on, until 'y. Directly entering lowercase greek letters is also possible. Caveat: do not use the leading prime for them.

Type constraint

You might need a leading space before a type constraint so the system would not try to interpret the leading colon as part of the previous symbol:

$= :bool->bool->bool

Lexing

HOL4 variable names should be identifiers:

  • alphanumerics with leading letter and possibly underscores inside
  • symbolic ASCII and Unicode identifiers: not letter and not digit Unicode characters are regarded as symbolic

Dollar

The dollar has a number of uses: single $ is a low-precedence right-associative function application operator borrowed from Haskell: FACT $ SUC $ 2 + 3 = FACT(SUC(2+3)) . Starting with $ only $-only symbolic identifiers are possible: $, $$, $$$ … . We can get rid of infix and other special syntax status of identifiers with a prefixing dollar: $< . Without $ HOL4 gives an error message, but this works:

> type_of ``$<``;
val it = “:num -> num -> bool”: hol_type

This is useful when we want to use the function as an argument. An alternative syntax for this is using parentheses: (<).

Last, $ is used as a separator between theory name and identifier, forming a long identifier: bool$COND .

Non-aggregeting characters

Characters in this class, unless there is a symbolic identifier defined with them, are lexed separately: parentheses, curly braces and square brackets: () {} [], tilde, dot, comma, semicolon and hyphen: ~ . , ; - . So, (( +; and -+ are treated as two characters. Non-aggregating Unicode characters are:

¬ U+00AC ⟨ U+27E8
⌈ U+2308 ⟩ U+27E9
⌉ U+2309 ⦃ U+2983
⌊ U+230A ⦄ U+2984
⌋ U+230B ⦇ U+2987
⟦ U+27E6 ⦈ U+2988
⟧ U+27E7

Whitespace

Space, Carriage Return, Line Feed, Tab and Form Feed are separators.

Numbers

A string of digits is a number. The underscore is a pseudo-digit to give spacing if needed: 3_000_000 . There is postfix notation for the num and the int type: 3n, 3i . For hexadecimal and binary numbers there are prefixal forms 0xAF12 and 0b1110 . For hexadecimals, besides A-F, lowercase a-f can be used as well.

Comments

Comments are written with standard SML delimiters: (* This is a comment *)

Parsing

In functional programming, interesting data structures are built using the constructors of data types. But, of course, it would be cumbersome to write

APP (APP (APP (IDENT "COND", IDENT "P"), IDENT "Q"), IDENT "R")

instead of

if P then Q else R

Overloading

HOL4 maintains an overload map from strings to lists of terms for parsing, and another one in the opposite direction for prettyprinting: from lists of terms to identifiers . This allows to use + for natural numbers, integers and words. Naturally, the use of overloaded terms might require type constraints.

Functions with special syntax

Functions mostly use curried postfix syntax for arguments: f x y z for a three-argument function f. But there is a possibility to use binder, prefix, suffix, infix, closefix and mixfix syntax.

A binder binds a variable. Technically, due to Church, this is done on applying a binder constant to a lambda abstraction. So ∀x.M is ∀(\x.M) . Consecutive application of the same binder can be shorthanded with only one instance of the binder: ∀x y z.M .

Infix operator op works as A op B. It can be left-associative, right-associative or non-associative.

For prefix operations, standard function application is a good example: f x .

Relation closures are good examples of suffix operators: R^+ for transitive closure of R.

Closefix operators are for delimiters: we can use [| x |] for a denotation semantics of x .

For mixfix syntax, the if-then-else clause and the [ t1 / t2 ] t3 notation for term substitution is a good example.

Quotation and antiquotation

Quotations are of type ‘a frag list and have the concrete syntax delimited with backticks: `f x = 3`. Parse.Term is of type term quotation -> term and it creates a term of a quotation.

A direct way of constructing such a term is to use double quotations: ``f x = 3`` . If the expression is meant to be a type, then it should begin with a colon: ``:’a -> bool``.

An advantage of quotations is the free use of newlines and backslash characters.

The escape character in quotations is the caret: ^ . The ^(t) syntax is for antiquotations: its aim is to embed ML type or term expressions. If the expression is an ML identifier, then the parentheses can be ommitted altogether.

Term grammar

So HOL4 has a parser that translates the surface syntax into primitive constructors. Similarly to type_grammar, function term_grammar gives us the current state of the grammar used in parsing. I inject explanations to the output of the command to make it understandable.

> term_grammar();
val it =
   (0)    TM  ::= "OLEAST" <..binders..> "." TM
                | "LEAST" <..binders..> "." TM | "some" <..binders..> "." TM
                | "∃!" <..binders..> "." TM [?!] | "?!" <..binders..> "." TM
                | "∃" <..binders..> "." TM [?] | "?" <..binders..> "." TM
                | "∀" <..binders..> "." TM [!] | "!" <..binders..> "." TM
                | "@" <..binders..> "." TM | "λ" <..binders..> "." TM
                | "\" <..binders..> "." TM

First, binders. If c is a binder, c x. t translates to $c \x.t, that is, the standard syntax version of the binder applied to the lambda abstraction λx.t . Also, c x1 x2 .. xN . t translates to c x1. c x2. .. c xN . t .

The LEAST binder works on functions defined for natural numbers, giving back the least number that satisfies its argument predicate: ∀P. $LEAST P = WHILE ($¬ ◦ P) SUC 0 . The while loop starts from 0 and goes until a successor satisfies predicate P. The binder OLEAST is an option valued binder that gives SOME n when LEAST would define a concrete value and NONE otherwise: ∀P. $OLEAST P = if ∃n. P n then SOME (LEAST n. P n) else NONE .

To explain “some”, take binder @, which is a higher order version of Hilbert’s choice operator ε, an element that satisfies a predicate P, given there is such an element: ∃x.P x ⇒ P(@x.P x) . The binder some (not to mistake for the SOME constructor of the option type) is ∀P. $some P = if ∃x. P x then SOME (@x. P x) else NONE, it tells us whether the choice operator would return a real value satisfying the relation.

Then comes the uniquely exists quantor ∃! and its ASCII counterpart ?!, the exists quantor ∃ and its ASCII version ?, the universal quantor ∀ and ASCII !, the lambda operator λ and its ASCII equivalent \ .

   (1)    TM  ::= "case" TM "of" TM "|" TM "|" TM
                    [case magic$default - %case%]
                | "case" TM "of" "|" TM  [case magic$default - %case%]
                | "case" TM "of" TM "|" TM  [case magic$default - %case%]
                | "case" TM "of" TM  [case magic$default - %case%]

The case construction. Here is an example that explains itself:

case n of
  0 => "none"
| 1 => "one"
| 2 => "two"
| _ => "many"

Writing a vertical bar in front of the first case 0 is also valid syntax.

   (4)    TM  ::= TM "::" TM (restricted quantification operator)
   (5)    TM  ::= TM TM  (binder argument concatenation)
   (8)    TM  ::= "let" LTM< _ letnil, _ letcons,;> "in" TM  [ _ let]
   (9)    TM  ::= TM "and" TM  [ _ and]   (L-associative)
   (12)   TM  ::= TM "=>" TM  [case magic$default - %arrow%]
                    (non-associative)

Restricted quantification is giving a bounding set for the values of the bound variable: (\ x :: P. M) = M[y/x] only if y ∈ P. Binder argument concatenation is the case I described above: we can write only one binder with a list of bound variables. The let construct is let v = M in N. The left-associative “and” construct is the one we can use in a let expression: let v = M and u = P in N. The arrow => is the mapping of a value to a pattern in a case construction, see the previous HOL4 example.

   (50)   TM  ::= TM "," TM     (R-associative)
   (70)   TM  ::= "if" TM "then" TM "else" TM  [COND]
   (80)   TM  ::= TM ":-" TM     (non-associative)
   (100)  TM  ::= TM "↦" TM  [  combinpp.leftarrow]
                | TM "|->" TM  [  combinpp.leftarrow] | TM "⇎" TM  [<=/=>]
                | TM "<=/=>" TM   | TM "⇔" TM  [<=>] | TM "<=>" TM
                    (non-associative)
   (200)  TM  ::= TM "⇒" TM  [==>] | TM "==>" TM     (R-associative)

The right-associative comma is the pair constructor. There’s no need for an explanation of the if-then-else construct that translates to the COND internal primitive, as seen above. An input syntax for the turnstile logical symbol is :- . It is used only in low-level definitions. The maplet symbol ↦ and its ASCII equivalent |-> is the finite map type operator. For logical equivalence and non-equivalence, there is ⇔ and ⇎, and their ASCII counterpart <=> and <=/=> . For logical implication there is ⇒ and the ASCII ==> .

   (300)  TM  ::= TM "∨" TM  [\/] | TM "\/" TM     (R-associative)
   (310)  TM  ::= TM ":>" TM     (L-associative)
   (320)  TM  ::= TM "=+" TM  [UPDATE]   (non-associative)
   (400)  TM  ::= TM "∧" TM  [/\] | TM "/\" TM     (R-associative)
   (425)  TM  ::= TM "refines" TM   | TM "partitions" TM
                | TM "equiv_on" TM   | TM "∉" TM  [NOTIN] | TM "NOTIN" TM
                | TM "∈" TM  [IN] | TM "IN" TM
                    (non-associative)

There is ∨ for logical or and its ASCII equivalent \/ . The :> operator is reverse function application: x :> f is f x. And it lets us write x :> g :> f for f(g(x)) . The =+ symbol is function update: (a =+ b) f updates f with value b at argument a . For logical and there is ∧ and its ASCII counterpart /\ . For two set of sets, A refines B if for every element set of A there is an element of B so that the former is a subset of the latter: for a ∈ A there is b ∈ B so that a ⊆ b . The infix equiv_on operator tells that a relation R is an equivalence relation on the underlying set S, that is, R is reflexive, symmetric and transitive. The expression X partitions Y holds when X is a set of sets, the partition of Y according to some equivalence relation R. For set membership and non-membership there is ∈ and ∉ and their ASCII notation IN and NOTIN .

   (450)  TM  ::= TM "≼" TM  [<<=] | TM "<<=" TM   | TM "PERMUTES" TM
                | TM "HAS_SIZE" TM   | TM "⊂" TM  [PSUBSET]
                | TM "PSUBSET" TM   | TM "⊆" TM  [SUBSET] | TM "SUBSET" TM
                | TM "≥" TM  [>=] | TM ">=" TM   | TM "≤" TM  [<=]
                | TM "<=" TM   | TM ">" TM   | TM "<" TM
                | TM "⊆ᵣ" TM  [RSUBSET] | TM "RSUBSET" TM   | TM "≠" TM
                | TM "<>" TM   | TM "=" TM
                    (non-associative)

The unicode symbol ≼ and its ASCII version <<= is for the list prefix partial order isPREFIX . The infix operator PERMUTES tells that PERMUTES f dom if f is a bijection on dom. For HAS_SIZE, A HAS_SIZE n if A is a finite set and has n elements. The notation for subset and proper subset is SUBSET and PSUBSET, and the usual symbols. For usual ordering relations HOL4 has the usual symbols. The operator RSUBSET is a subset relation for relations. Equality and non-equality is again the usual.

   (460)  TM  ::= TM "$" TM  [_ fnapp] | TM "with" TM  [record update]
                | TM ":=" TM  [record field update]
                | TM "updated_by" TM  [functional record update]
                    (R-associative)

The dollar symbol is a low-precedence function application symbol: FACT $ SUC $ 2 + 3 = FACT(SUC(2+3)), spares a few parentheses. This is a Haskell notation. Other than solo dollar, dollar immediately followed by a possibly symbolic identifier returns a normal function that can be used as a function argument: ASCII tilde, ~ is negation, $~ is a usable name for a function argument. When the identifier is infix, dollar removes its infix status and the combined identifier can be used as a prefix function name.

The keyword with and symbolic identifier := can be used in a record field update: rec with fieldname := new_value . Using updated_by allows us to apply a function to a record field: bob with employed updated_by $~ , this inverts the boolean field employed. And, it is right-associative.

   (480)  TM  ::= TM "⧺" TM  [++] | TM "++" TM   | TM "+++" TM
                    (L-associative)
   (490)  TM  ::= TM "::" TM  [CONS] | TM "INSERT" TM
                | TM "###" TM  [PAIR_REL] | TM "LEX" TM   | TM "##" TM
                    (R-associative)

The ASCII double plus, ++ operator and its Unicode variant ⧺ is list append. The triple plus +++ is an operator on relations: its type is :(α -> β -> bool) -> (γ -> δ -> bool) -> α + γ -> β + δ -> bool, so it creates a componentwise sum type for the two relations.

The double colon :: is the cons operation as suggested: extending a list with an element on the left. The keyword INSERT is set insertion: 1 INSERT {1;3} = {1;3} . The triple hash mark ### operates on two relations and gives back a relation that is of componentwise products of the original relations’ base types: :α # γ -> β # δ -> bool , from :α -> β -> bool and :γ -> δ -> bool .

   (500)  TM  ::= TM "⊔" TM  [disjUNION] | TM "<+>" TM  [disjUNION]
                | TM "DELETE" TM   | TM "DIFF" TM   | TM "∪" TM  [UNION]
                | TM "UNION" TM   | TM "<*>" TM  [APPLICATIVE_FAPPLY]
                | TM "−" TM  [-] | TM "-" TM   | TM "+" TM
                | TM "∪ᵣ" TM  [RUNION] | TM "RUNION" TM
                    (L-associative)
   (600)  TM  ::= TM "∩" TM  [INTER] | TM "INTER" TM   | TM "\\" TM
                | TM "DIV" TM   | TM "*" TM   | TM "∩ᵣ" TM  [RINTER]
                | TM "RINTER" TM
                    (L-associative)
   (601)  TM  ::= TM "×" TM  [CROSS] | TM "CROSS" TM   | TM "⊗" TM  [*,]
                | TM "*," TM
                    (R-associative)

The <+> and its Unicode counterpart ⊔ is disjoint union for relations: for types :α -> bool and :β -> bool it gets :α + β -> bool. The infix operator DELETE deletes an element from a set. The next one, DIFF is set difference. The meaning of UNION and its Unicode variant ∪ is obviously set union. The infix operator <*>, nicknamed APPLICATIVE_FAPPLY applies all functions in a function list to a list of arguments and concatenates the resulting lists: [(λx. x + 2); (λx. x + 4)] <*> [1; 2; 3] = [3; 4; 5; 5; 6; 7] .

The Unicode minus sign seems to be just an alias to standard ASCII minus sign - . The ASCII plus + is addition for natural numbers, of type num. The infix operator RUNION or its Unicode equivalent ∪ᵣ lifts set union operation to relations, and so does RINTER and ∩ᵣ for intersection. The infix INTER and its Unicode counterpart ∩ is obviously set intersection. The infix \\ is an operator on finite maps, it removes the right argument from the domain of the finite map on the left.

The infix DIV is obviously natural number division, and ASCII star is natural number multiplication. The infix CROSS and its Unicode notation × creates a cross product of two sets, on other words, a set of pairs: :α # β -> bool . Remember that hash mark # is for the product type. The infix ASCII *, operator on natural numbers and its Unicode variant ⊗ allows us to encode two natural numbers in one through triangular numbers. It is possible then to decode the first and second number from this encoded value.

   (650)  TM  ::= TM "%%" TM   | TM "MOD" TM     (L-associative)
   (700)  TM  ::= TM "**" TM   | TM "EXP" TM     (R-associative)

The symbolic identifier %% looks like a modulo operation before MOD but it is in fact a derived operation: CEILING_MOD m n = (n - m MOD n) MOD n . MOD is the usual modulo operation. The standard EXP and its symbolic counterpart ** is natural number exponentiation.

   (800)  TM  ::= TM "∘ᵣ" TM  [O] | TM "O" TM   | TM "∘" TM  [o]
                | TM "o" TM
                    (R-associative)

Capital O and its symbolic counterpart ∘ᵣ is relation compióosition, while small o and Unicode ∘ is function composition.

   (899)  TM  ::= TM ":" TY  (type annotation)
   (900)  TM  ::= "&" TM   | "-" TM  [numeric_negate] | "¬" TM   | "~" TM
   (2000) TM  ::= TM TM  (function application)   (L-associative)

With a weak precedence, colon : is for type annotation. Ampersand, &, is for converting from num to int, provided integerTheory is loaded. It is also of internal use for nat_elim__magic. The standard minus sign is numeric negation. The ASCII tilde ~ and its Unicode counterpart ¬ is for boolean negation. Function application, writing terms consecutively with only whitespace has a high precedence to allow one-argument function applications to write without parentheses.

   (2100) TM  ::= TM "³"   | TM "²"   | TM "ᵀ"  [relinv] | TM "^="  [EQC]
                | TM "꙳"  [RTC] | TM "^*"  [RTC] | TM "⁺"  [TC]
                | TM "^+"  [TC]
                | TM "⦇" LTM<  combinpp.nil,  combinpp.cons,;> "⦈"
                    [  combinpp.toplevelupd]
                | TM "(|" LTM<  combinpp.nil,  combinpp.cons,;> "|)"
                    [  combinpp.toplevelupd]

The superscripts ()³ and ()² are obviously cubic and quadratic powers. The superscript T, Unicode ()ᵀ is the inverse of a relation. The following are closures of a relation of type A x A , for it is meaningful if we can compose the relation with itself. Equality closure is ^=, reflexive transitive closure is ^* and Unicode ꙳, and transitive closure is ^+ or Unicode ()⁺ .

The delimiters (| and |) and their Unicode equivalent ⦇ and ⦈ are for function update: (\x.x) (| 2 |-> 3 |) creates a function that is the identity function on natural numbers, except that for argument 2 it gives back 3. And so does the Unicode notation (λx. x)⦇2 ↦ 3⦈ .

   (2200) TM  ::= "𝕌" TM   | "univ" TM
   (2500) TM  ::= TM "." TM  [record field selection]   (L-associative)
          TM  ::= "[" LTM<NIL,CONS,;> "]"  [ListForm<NIL,CONS>]
                | "{" LTM<EMPTY,INSERT,;> "}"  [ListForm<EMPTY,INSERT>]
                | "{" TM "|" TM "|" TM "}"  [gspec2 special]
                | "{" TM "|" TM "}"  [gspec special] | "(" ")"  [one]
                | "<|" LTM< _ record nil, _ record cons,;> "|>"
                    [ListForm< _ record nil, _ record cons>]
                | "(" TM ")"  [just parentheses, no term produced]

The identifier univ and Unicode 𝕌 is for the universal set of a type. Its definition is ⊢ 𝕌(:α) = (λx.T), in other words, any element of the type is an element of the universal set. The usual dot notation is for record field selection, rec.fieldname . Square brackets with semicolons are for lists: type_of [1;2;3]; returns “:num list”. Curly braces with semicolons are for concrete sets: type_of {1;2;3}; returns “:num -> bool”. Sets are modelled with their characteristic function on their base type. Curly braces with vertical bars allow us to write set comprehensions: ⊢ ∀s t. s DIFF t = {x | x ∈ s ∧ x ∉ t} . The usual hermite () is the only element of type one and the isomorphic type unit.

Record specifications and values can be written with <| and semicolons and |> : for a specification let’s have

Datatype: person = <| employed : bool ; age : num ; name : string |>
End

and for a value let’s have

<| age := 21; employed := F; name := "Layabout" |>

The following output does not convey new information about symbolic identifiers so I trimmed it:

   Known constants:
       _ fakeconst4.case,S10.case magic,7.default ! ## %% & () * ** *, + ++
     +++ , - /\ 0 :- :> < <<= <= <=/=> <=> <> = ==> > >= ? ?! @ ABS_DIFF

The next section gives some help on assigning meaning to symbols. I omit the already discussed symbolic identifiers and alphabetic identifiers that are easy to find in the source code.

First, there are some spooky identifiers that wouldn’t print:

   Overloading:
       <won't print>    ->  λ(x :α). list$CONS x (list$NIL :α list)
                            λ(h :α) (l :α list).
                                bool$~ (bool$IN h (list$LIST_TO_SET l))
                            λ(x :α itself). 𝕌(:α)

A properly constructed grep command gives us the empty string definitions for these:

$ grep 'overload_on.*""' -R HOL/src
HOL/src/pred_set/src/pred_setScript.sml:val _ = overload_on ("", “\x:'a itself. UNIV : 'a set”)
HOL/src/list/src/listScript.sml:val _ = overload_on ("", “\h:'a l:'a list. ~(h IN LIST_TO_SET l)”)
HOL/src/list/src/listScript.sml:val _ = overload_on("", “\x:'a. [x]”)

The first thing in the term_grammar output that won’t print is the [x] notation for having a one-element list, see the third line in grep output.

The second non-printing grammar element in the term grammar is the second in grep output. I give context to it in HOL/src/list/src/listScript.sml :

val _ = overload_on ("", “\h:'a l:'a list. ~(h IN LIST_TO_SET l)”)
  (* last over load here causes the term ~(h IN LIST_TO_SET l) to not print
     using overloads.  In particular, this prevents the existing overload for
     NOTIN from firing in this type instance, and allows ~MEM a l to print
     because the pretty-printer will traverse into the negated term (printing
     the ~), and then the MEM overload will "fire".
  *)

So, ~MEM is expressing that an element does not appear in a list, converting the list to a set internally.

Digging the code for the third we meet a technicality in HOL/src/pred_set/src/pred_setScript.sml, the first hit in grep output:

val _ = overload_on ("univ", ``\x:'a itself. UNIV : 'a set``)
val _ = set_fixity "univ" (Prefix 2200)

val _ = overload_on (UnicodeChars.universal_set, ``\x:'a itself. UNIV: 'a set``)
val _ = set_fixity UnicodeChars.universal_set (Prefix 2200)
(* the overloads above are only for parsing; printing for this is handled
   with a user-printer.  (Otherwise the fact that the x is not bound in the
   abstraction produces ARB terms.)  To turn printing off, we overload the
   same pattern to "" *)
val _ = overload_on ("", “\x:'a itself. UNIV : 'a set”)

so non-printing has an internal technical reason here: that is, pretty printing.

     \\                 ->  arithmetic$CEILING_DIV
                            (relation$RDOM_DELETE :(α -> β -> bool) ->
                                                   α -> α -> β -> bool)

The symbolic identifier \\ overloads for two purposes: for the arithmetic CEILING_DIV: CEILING_DIV m n = (m + (n - 1)) DIV n and RDOM_DELETE, which removes an element from the domain, i.e. the left supporting set of a relation.

     tri⁻¹              ->  numpair$invtri

The Unicode superscript -1 is the inverse of the triangle number function defined in HOL/src/num/extra_theories/numpairScript.sml .

     Π                  ->  (pred_set$PROD_IMAGE :(α -> num) ->
                                                  (α -> bool) -> num)
     ∅                  ->  (pred_set$EMPTY :α -> bool)
     ∅ᵣ                 ->  (relation$EMPTY_REL :α -> α -> bool)
     ∑                  ->  (pred_set$SUM_IMAGE :(α -> num) ->
                                                 (α -> bool) -> num)

Capital Pi is the usual product on natural numbers, whose first argument is a number-valued function on domain α that is corresponding to the formula, and the second argument is the characteristic function on the same domain α representing the set the production goes over.

Unicode ∅ and ∅ᵣ is for the empty set and the empty relation, respectively.

Capital Sigma is similar in its arguments to Pi, but this is for summing natural numbers.

Unicode

With a properly configured terminal connection, vim and font installation, the editor will display the above Unicode symbols properly. But there is a question: how to enter these Unicode symbols.

In hol-vim, these symbols can be entered through digraphs. See the character combinations for them in holabs.vim in HOL4’s github repository. To enter mathematical symbols with digraphs, we need to push CTRL-K and then the two-letter digraph. Here is a full list of mathematical Unicode characters in hol-vim and their corresponding digraphs:

∧ AN conjunction
∨ OR disjunction
¬ NO -, negation
⇒ => implication
≤ =< less or equal
≥ >= greater or equal
⇔ == bi-implication, if and only if
≠ != not equal
∀ FA universal quantor, for all
∃ TE existential quantor, exists
λ l* the lambda operator
∈ (- element of
+ (+ not an element, this did not work,
     use ^Vu2209 instead for ∉, HOL4 will understand it
∩ (U meet of sets
∪ U) union of sets
⊆ (_ subset (might be equal)
⊂ (C proper subset (cannot be equal)