To complement András' answer, and especially if you want to stay in Coq, you can also define such an internal universe using indexed inductive types only. Basically, you replace the definitions of both U
and El
by a unique indexed inductive type, from which you can recover U
and El
after the fact.
Record Unit : Set := {}.
#[universes(polymorphic), projections(primitive)]
Record Σ {A : Type} {B : A -> Type} : Type :=
{ fst : A ; snd : B fst }.
Arguments Σ : clear implicits.
Inductive Udec : Set -> Type@{Set + 1} :=
| _unit : Udec Unit
| _sigma {ElA : Set} (A : Udec ElA) {ElB : ElA -> Set}
(B : forall a, Udec (ElB a)) : Udec (Σ ElA ElB).
Definition U@{i | Set < i } : Type@{i} := Σ@{i i} Set Udec.
Definition El (code : U) : Set := code.(fst).
Definition unit : U :=
{| fst := _ ; snd := _unit |}.
Definition sigma (A : U) (B : El A -> U) : U :=
{| fst := _; snd := _sigma A.(snd) (fun a => (B a).(snd)) |}.
Note that I have been somewhat explicit with universes, to showcase the fact that this encoding needs to bump universe levels: U
cannot live at the lower level (Set
) in this version, while it can in the inductive-recursive one. This is because the constructors of Udec
must contain types.