(* follow me on https://twitter.com/TheEduardoRFS *)

module Existentials = struct
  (* packed *)

  type show = Ex_show : { content : 'a; show : 'a -> string } -> show

  let value = Ex_show { content = 1; show = Int.to_string }

  let eval_show ex =
    let (Ex_show { content; show }) = ex in
    show content

  (* well but first-class modules *)

  module type Show = sig
    type t

    val content : t
    val show : t -> string
  end

  let eval_show package =
    let (module S : Show) = package in
    S.show S.content

  let value =
    (module struct
      type t = int

      let content = 1
      let show = Int.to_string
    end : Show)
end

module Equality = struct
  type (_, _) eq = Refl : ('x, 'x) eq

  (* weak, how to derive sym? *)
  let cast : type a b. (a, b) eq -> a -> b =
   fun eq x ->
    let Refl = eq in
    x

  (* this is how I got here *)
  module Subst (M : sig
    type 'a t
  end) =
  struct
    let subst : type a b. (a, b) eq -> a M.t -> b M.t =
     fun eq x ->
      let Refl = eq in
      x
  end

  (* refutation *)
  let bad : type a. (string, int) eq -> a = fun eq -> match eq with _ -> .

  (* fancier *)
  let dependent_types :
      type a. ((a, int) eq, (a, bool) eq) Either.t -> a -> string =
   fun tag content ->
    match tag with
    | Left Refl -> Int.to_string content
    | Right Refl -> Bool.to_string content

  let show_int = dependent_types (Left Refl)
  let show_string = dependent_types (Right Refl)
end

module Show = struct
  (* this is just many equalities *)
  type _ ty =
    | T_int : int ty
    | T_bool : bool ty
    (* but recursive *)
    | T_list : 'a ty -> 'a list ty

  let rec show : type a. a ty -> a -> string =
   fun tag content ->
    match tag with
    | T_int -> Int.to_string content
    | T_bool -> Bool.to_string content
    | T_list el_tag ->
        let serialized_content = List.map (fun el -> show el_tag el) content in
        Format.sprintf "[%s]" (String.concat ", " serialized_content)

  let () = print_endline @@ show T_int 1
  let () = print_endline @@ show (T_list T_bool) [ true; false ]
end

module Dependent = struct
  (* the dream *)

  type ty = T_int | T_bool | T_list of ty

  (* sadly this doesn't work *)


  type 'tag to_type =
    [%d
      match tag with
      | T_int -> int
      | T_bool -> bool
      | T_list el -> list (to_type el)]

  let rec show tag (content : tag to_type) =
    match tag with
    | T_int -> Int.to_string content
    | T_bool -> Bool.to_string content
    | T_list el_tag ->
        let serialized_content = List.map (fun el -> show el_tag el) content in
        Format.sprintf "[%s]" (String.concat ", " serialized_content)

  let () = print_endline @@ show T_int 1
  let () = print_endline @@ show (T_list T_bool) [ true; false ]
end

module But_some_duplication = struct
  (* duplicating the type definition also on the type level *)
  type t_int = |
  type t_bool = |
  type _ t_list = |

  type _ ty =
    | T_int : t_int ty
    | T_bool : t_bool ty
    | T_list : 'el ty -> 'el t_list ty

  (* expand syntax *)

  type (_, _) to_type_w =
    | W_t_int : (t_int, int) to_type_w
    | W_t_bool : (t_bool, bool) to_type_w
    | W_t_list : ('tag_el, 'r) to_type_w ->
      ('tag_el t_list, 'r list) to_type_w

  (* add the original as a package *)
  type _ to_type = Ex : ('tag, 'r) to_type_w -> 'tag to_type

  (* term level version *)
  let rec to_type : type tag. tag ty -> tag to_type =
   fun tag ->
    match tag with
    | T_int -> Ex W_t_int
    | T_bool -> Ex W_t_bool
    | T_list el ->
        let (Ex el_w) = to_type el in
        Ex (W_t_list el_w)


  let rec show :
      type tag content. (tag, content) to_type_w ->
          tag ty -> content -> string
      =
   fun content_type tag content ->
    match tag with
    | T_int ->
        let W_t_int = content_type in
        Int.to_string content
    | T_bool ->
        let W_t_bool = content_type in
        Bool.to_string content
    | T_list el_tag ->
        let (W_t_list el_type) = content_type in
        let serialized_content =
          List.map (fun el -> show el_type el_tag el) content
        in
        Format.sprintf "[%s]" (String.concat ", " serialized_content)

  let () =
    let tag = T_int in
    let (Ex (type content) (w : (_, content) to_type_w)) = to_type tag in
    let content : content =
      let W_t_int = w in
      1
    in
    print_endline @@ show w tag content

  let () =
    let tag = T_list T_bool in
    let (Ex (type content) (w : (_, content) to_type_w)) = to_type tag in
    let content : content =
      let (W_t_list W_t_bool) = w in
      [ true; false ]
    in
    print_endline @@ show w tag content
end

module Compute_anything = struct
  type z = |
  type _ s = |
  type _ nat = Z : z nat | S : 'a nat -> 'a s nat
  type (_, _) eq = Refl : ('x, 'x) eq

  type ('n, 'm, 'r) add_w =
    | W_n_z : (z, 'm, 'm) add_w
    | W_n_s : ('n, 'm, 'r) add_w -> ('n s, 'm, 'r s) add_w

  type ('n, 'm) add = Ex : 'r nat * ('n, 'm, 'r) add_w -> ('n, 'm) add

  let rec add : type n m. n nat -> m nat -> (n, m) add =
   fun n m ->
    match n with
    | Z -> Ex (m, W_n_z)
    | S n ->
        let (Ex (r, w)) = add n m in
        Ex (S r, W_n_s w)

  let five =
    let (Ex (r, w)) = add (S (S Z)) (S (S (S Z))) in
    let (W_n_s (W_n_s W_n_z)) = w in
    (r : z s s s s s nat)

  (* n + z = n *)
  let rec add_z_is_id : type n r. n nat -> (n, z, r) add_w -> (n, r) eq =
   fun n w ->
    match n with
    | Z ->
        let W_n_z = w in
        Refl
    | S n ->
        let (W_n_s w) = w in
        let Refl = add_z_is_id n w in
        Refl
end