module Cat.Instances.Assemblies where

Assemblies over a PCA🔗

When working over a partial combinatory algebra it’s often the case that we’re interested in programs as concrete implementations of some mathematical datum For example, we can implement the successor function on natural numbers to be representing a numeral as a Church numeral, taking the defining property of to be that if we have some iterable process starting at then the iteration is applied to the iteration; But we could just as well implement representing a numeral as a Curry numeral, a pair containing the information of whether the number is zero and its predecessor (if any). These implementations are extensionally identical, in that they both denote the same actual natural number, but for a concrete pca they might genuinely be different — we could imagine measuring the time complexity of the predecessor function, which is for Curry numbers and for Church numbers. Therefore, if we are to investigate the computational content of constructive mathematics, we need a way to track the connection between the mathematical elements and the programs which denote them.

An assembly over a pca is a set equipped with a propositional relation between values and elements when this holds, we say realises Moreover, for every we require that there be at least one which realises it.

\ Warning

The construction of assemblies over and the category works regardless of which pca we choose — but we only get something interesting if is nontrivial: the category over the trivial pca is the category

Therefore, when making natural-language statements about we generally assume that is nontrivial. A statement like “the category is not univalent” should be read as saying “univalence of implies is trivial.”

record Assembly (𝔸 : PCA ℓA)  : Type (lsuc   ℓA) where
  no-eta-equality
  field
    Ob         : Type 
    has-is-set : is-set Ob
    realisers  : Ob  ℙ⁺ 𝔸
    realised   :  x  ∃[ a    𝔸  ] (a  realisers x)

A prototypical example is the assembly of booleans, 𝟚, defined below. Its set of elements is Bool, and we fix realisers

see booleans in a pca for the details of the construction. This is not the only possible choice: we could, for example, say that the value true is implemented by the program (and vice-versa). This results in a genuinely different assembly over Bool1, though with the same denotational data.

To understand the difference— and similarity— between the ordinary assembly of booleans and the swapped booleans, we define a morphism of assemblies to be a function satisfying the property that there exists a program which sends realisers of to realisers of

record Assembly-hom {𝔸 : PCA ℓA} (X : Assembly 𝔸 ) (Y : Assembly 𝔸 ℓ') : Type (ℓA    ℓ') where
  open Realisability.Base 𝔸 using ([_]_⊢_)

  field
    map     :  X    Y 
    tracked :  [ map ] X .realisers  Y .realisers 

Note the force of the propositional truncation in this definition: maps of assemblies are identical when they have the same underlying function, regardless of which programs potentially implement them. Since we can not, for a general show that the programs and are identical, would not be a category if the choice of realiser mattered for identity of computable maps.

This consideration is necessary for assemblies and assembly morphisms to be a category: in an arbitrary pca composition of programs need not be unital or associative.

private unquoteDecl eqv = declare-record-iso eqv (quote Assembly-hom)

instance
  H-Level-Assembly-hom :  {n}  H-Level (Assembly-hom X Y) (2 + n)
  H-Level-Assembly-hom = basic-instance 2 $ Iso→is-hlevel 2 eqv (hlevel 2)

  Extensional-Assembly-hom
    :  {ℓr}  _ : Extensional ( X    Y ) ℓr 
     Extensional (Assembly-hom X Y) ℓr
  Extensional-Assembly-hom  e  = injection→extensional!  p  Iso.injective eqv (Σ-prop-path! p)) e

  Funlike-Assembly-hom : Funlike (Assembly-hom X Y)  X  λ _   Y 
  Funlike-Assembly-hom = record { _·_ = Assembly-hom.map }

{-# DISPLAY Assembly-hom.map f x = f · x #-}

-- Helper record for constructing an assembly map when the realiser is
-- known/does not depend on other truncated data.

record make-assembly-hom {𝔸 : PCA ℓA} (X : Assembly 𝔸 ) (Y : Assembly 𝔸 ℓ') : Type (ℓA    ℓ') where
  open Realisability.PCA.Sugar 𝔸 using (_⋆_)
  field
    map      :  X    Y 
    realiser : ↯⁺ 𝔸
    tracks   : {x :  X } {a :   𝔸 } (ah : [ X ] a  x)  [ Y ] realiser  a  map x

open Assembly-hom public

to-assembly-hom
  :  {𝔸 : PCA ℓA} {X : Assembly 𝔸 } {Y : Assembly 𝔸 ℓ'}
   make-assembly-hom X Y
   Assembly-hom X Y
{-# INLINE to-assembly-hom #-}

to-assembly-hom f = record { make-assembly-hom f using (map) ; tracked = inc record { make-assembly-hom f } }

module _ (𝔸 : PCA ℓA) where
  open Realisability.Base 𝔸
  open Realisability.PCA.Sugar 𝔸
  open Realisability.Data.Bool 𝔸

  open Assembly-hom
  open Precategory
  Assemblies :    Precategory (lsuc   ℓA) (ℓA  )
  Assemblies  .Ob      = Assembly 𝔸 
  Assemblies  .Hom     = Assembly-hom
  Assemblies  .Hom-set x y = hlevel 2
  Assemblies  .id      = record where
    map x   = x
    tracked = inc id⊢
  Assemblies  ._∘_ f g = record where
    map x   = f · (g · x)
    tracked =  f .tracked ∘⊢ g .tracked 
  Assemblies  .idr   f     = ext λ _  refl
  Assemblies  .idl   f     = ext λ _  refl
  Assemblies  .assoc f g h = ext λ _  refl
\ Warning

Unlike most other categories constructed on the 1Lab, the category of assemblies is not univalent; see univalence of categories of assemblies.

The assembly of booleans🔗

The assembly of booleans, is the simplest example of an assembly which contains actual computability data. Its construction is entirely straightforward:

  𝟚 : Assembly 𝔸 lzero
  𝟚 .Ob          = Bool
  𝟚 .has-is-set  = hlevel 2
  𝟚 .realisers true  = singleton⁺ `true
  𝟚 .realisers false = singleton⁺ `false
  𝟚 .realised  true  = inc (`true .fst , inc refl)
  𝟚 .realised  false = inc (`false .fst , inc refl)

We define the realisability relation as a function from Bool, by cases: the only option for realising the boolean true is with the `true program, and similarly the false boolean is realised by the `false program.

Indiscrete assemblies🔗

However, the assembly of booleans is not the only assembly we can construct on the type of booleans. As mentioned above, we could also have inverted which program realises each boolean; but this is still an assembly with nontrivial computability data. Now, we show that the “ambient” world of sets and functions embeds fully faithful into the category of assemblies over any pca

This is, perhaps, a bit surprising: maps of assemblies are computable by definition, but arbitrary functions between sets need not be! The catch is that, when equipping a set with the structure of an assembly, we get to choose which programs compute which elements; and, above, we have made a sensible choice. But we can always make an adversarial choice, letting every program at all realise any element.

Terminology

We denote the indiscrete assembly on a set as following the literature. Note however that Bauer (2022) refers to these as constant assemblies, while de Jong (2024) does not assign them a name but merely singles them out as embedding classical logic in

   :  {} (X : Type )  _ : H-Level X 2   Assembly 𝔸 
   X .Ob          = X
   X .has-is-set  = hlevel 2
   X .realisers x = defineds
   X .realised  x = inc (expr  x  x , abs↓ _ _)

The important thing to know about these is that any function of sets extends to a computable map of assemblies — this is because the only requirement for is that is defined, and assemblies are defined so that if then is defined.

  extend
    :  { ℓ'} {X : Assembly 𝔸 } {Y : Type ℓ'}  _ : H-Level Y 2 
     ( X   Y)  Assembly-hom X ( Y)
  extend {X = X} f = to-assembly-hom record where
    map x     = f x
    realiser  = val  x  x
    tracks ha = subst ⌞_⌟ (sym (abs-β _ [] (_ , X .def ha))) (X .def ha)
Following the general logic of adjoint functors, this means that is a functor for any at all — and moreover that is a right adjoint to the functor which projects the underlying set of each assembly.
  Cofree : Functor (Sets ) (Assemblies )
  Cofree .F₀ X    =   X 
  Cofree .F₁ f    = extend f
  Cofree .F-id    = ext λ _  refl
  Cofree .F-∘ f g = ext λ _  refl

  Forget : Functor (Assemblies ) (Sets )
  Forget .F₀ X    = el!  X 
  Forget .F₁ f    = f ·_
  Forget .F-id    = refl
  Forget .F-∘ f g = refl

  Forget⊣∇ : Forget {}  Cofree
  Forget⊣∇ .unit .η X = extend λ x  x
  Forget⊣∇ .unit .is-natural x y f = ext λ _  refl
  Forget⊣∇ .counit .η X a = a
  Forget⊣∇ .counit .is-natural x y f = refl
  Forget⊣∇ .zig = refl
  Forget⊣∇ .zag = ext λ _  refl

The indiscrete assemblies are generally poor as domains for computable functions, since a realiser for would have to choose realisers for given no information about Indeed, we can show that if there are non-constant maps then is trivial.

  non-constant-nabla-map
    : (f : Assembly-hom ( Bool) 𝟚)
     f · true  f · false
     is-trivial-pca 𝔸
  non-constant-nabla-map f x = case f .tracked of λ where
    record { realiser = (fp , f↓) ; tracks = t } 
      let
        a = t {true}  {`true .fst} (`true .snd)
        b = t {false} {`true .fst} (`true .snd)

        cases
          :  b b' (x :   𝔸 )
           [ 𝟚 ] x  b  [ 𝟚 ] x  b'
           b  b'  `true .fst  `false .fst
        cases = λ where
          true true   p  rec! λ rb rb' t≠t  absurd (t≠t refl)
          true false  p  rec! λ rb rb' _    rb  sym rb'
          false true  p  rec! λ rb rb' _    rb'  sym rb
          false false p  rec! λ rb rb' f≠f  absurd (f≠f refl)
      in cases (f · true) (f · false) _ a b x

  1. The assembly 𝟚 and its “flipped” variant obtained by swapping which boolean program realises each boolean value are isomorphic (even identical) in the category of assemblies, since the `not program is a computable involution.

    They are only distinct if considered as “assemblies over Bool”, where (following the logic of vertical maps) we restrict our attention to the isomorphisms with underlying function the identity.↩︎


References