T ::=
  | Int
  | Bool
t1, t2 ::=
  | 0
  | t1 + t2
  | true
  | false
The inference rules for the typing judgement, ⊢ t : T, are defined below:
────────────
 ⊢ n : Int
 ⊢ t1 : Int    ⊢ t2 : Int
──────────────────────────
    ⊢ t1 + t2  : Int
───────────────     ────────────────
 ⊢ true : Bool       ⊢ false : Bool
Prolog lets you prototype the inference rules in a very succinct way - but it can be a bit error-prone and brittle due to the lack of type and mode checking! The has_type/2 predicate can run in multiple 'modes' - for example as a type checker, or a type synthesizer. You could even use it to generate terms based on a given type.
has_type(int(Num), int).
has_type(add(Tm1, Tm2), int) :-
    has_type(Tm1, int),
    has_type(Tm2, int).
has_type(true, bool).
has_type(false, bool).Here we port the inference rules to an inductive proposition, and a couple of implementations (either a type checker or a type synthesizer).
inductive Ty :=
  | bool : Ty
  | int : Ty
inductive Tm :=
  | int : Int -> Tm
  | add : Tm -> Tm -> Tm
  | true : Tm
  | false : Tm
inductive HasType : Tm -> Ty -> Prop :=
  | int {n} : HasType (Tm.int n) Ty.int
  | add {tm1 tm2} :
      HasType tm1 Ty.int ->
      HasType tm2 Ty.int ->
      HasType (Tm.add tm1 tm2) Ty.int
  | true : HasType Tm.true Ty.bool
  | false : HasType Tm.false Ty.booldef has_type : Tm -> Ty -> Bool
  | Tm.int n,       Ty.int  => true
  | Tm.add tm1 tm2, Ty.int  => has_type tm1 Ty.int && has_type tm2 Ty.int
  | Tm.true,        Ty.bool => true
  | Tm.false,       Ty.bool => true
  | _,              _       => falsedef type_of : Tm -> Option Ty
  | Tm.int n => some Ty.int
  | Tm.add tm1 tm2 => 
      match type_of tm1, type_of tm2 with
        | some Ty.int, some Ty.int => some Ty.int
        | _,           _           => none
  | Tm.true => some Ty.bool
  | Tm.false => some Ty.bool