open! Import

module Test_applicative_s (A : Applicative.S with type 'a t := 'a Or_error.t) :
  Applicative.S with type 'a t := 'a Or_error.t = struct
  let error = Or_error.error_string
  let return = A.return

  let%expect_test _ =
    print_s [%sexp (return "okay" : string Or_error.t)];
    [%expect {| (Ok okay) |}]
  ;;

  let apply = A.apply

  let%expect_test _ =
    let test x y = print_s [%sexp (apply x y : string Or_error.t)] in
    test (Ok String.capitalize) (Ok "okay");
    [%expect {| (Ok Okay) |}];
    test (error "not okay") (Ok "okay");
    [%expect {| (Error "not okay") |}];
    test (Ok String.capitalize) (error "not okay");
    [%expect {| (Error "not okay") |}];
    test (error "no fun") (error "no arg");
    [%expect {| (Error ("no fun" "no arg")) |}]
  ;;

  let ( <*> ) = A.( <*> )

  let%expect_test _ =
    let test x y = print_s [%sexp (x <*> y : string Or_error.t)] in
    test (Ok String.capitalize) (Ok "okay");
    [%expect {| (Ok Okay) |}];
    test (error "not okay") (Ok "okay");
    [%expect {| (Error "not okay") |}];
    test (Ok String.capitalize) (error "not okay");
    [%expect {| (Error "not okay") |}];
    test (error "no fun") (error "no arg");
    [%expect {| (Error ("no fun" "no arg")) |}]
  ;;

  let ( *> ) = A.( *> )

  let%expect_test _ =
    let test x y = print_s [%sexp (x *> y : string Or_error.t)] in
    test (Ok ()) (Ok "kay");
    [%expect {| (Ok kay) |}];
    test (error "not okay") (Ok "kay");
    [%expect {| (Error "not okay") |}];
    test (Ok ()) (error "not okay");
    [%expect {| (Error "not okay") |}];
    test (error "no fst") (error "no snd");
    [%expect {| (Error ("no fst" "no snd")) |}]
  ;;

  let ( <* ) = A.( <* )

  let%expect_test _ =
    let test x y = print_s [%sexp (x <* y : string Or_error.t)] in
    test (Ok "okay") (Ok ());
    [%expect {| (Ok okay) |}];
    test (error "not okay") (Ok ());
    [%expect {| (Error "not okay") |}];
    test (Ok "okay") (error "not okay");
    [%expect {| (Error "not okay") |}];
    test (error "no fst") (error "no snd");
    [%expect {| (Error ("no fst" "no snd")) |}]
  ;;

  let both = A.both

  let%expect_test _ =
    let test x y = print_s [%sexp (both x y : (string * string) Or_error.t)] in
    test (Ok "o") (Ok "kay");
    [%expect {| (Ok (o kay)) |}];
    test (error "not okay") (Ok "kay");
    [%expect {| (Error "not okay") |}];
    test (Ok "o") (error "not okay");
    [%expect {| (Error "not okay") |}];
    test (error "no fst") (error "no snd");
    [%expect {| (Error ("no fst" "no snd")) |}]
  ;;

  let map = A.map

  let%expect_test _ =
    let test x = print_s [%sexp (map x ~f:String.capitalize : string Or_error.t)] in
    test (Ok "okay");
    [%expect {| (Ok Okay) |}];
    test (error "not okay");
    [%expect {| (Error "not okay") |}]
  ;;

  let ( >>| ) = A.( >>| )

  let%expect_test _ =
    let test x = print_s [%sexp (x >>| String.capitalize : string Or_error.t)] in
    test (Ok "okay");
    [%expect {| (Ok Okay) |}];
    test (error "not okay");
    [%expect {| (Error "not okay") |}]
  ;;

  let map2 = A.map2

  let%expect_test _ =
    let test x y = print_s [%sexp (map2 x y ~f:( ^ ) : string Or_error.t)] in
    test (Ok "o") (Ok "kay");
    [%expect {| (Ok okay) |}];
    test (error "not okay") (Ok "kay");
    [%expect {| (Error "not okay") |}];
    test (Ok "o") (error "not okay");
    [%expect {| (Error "not okay") |}];
    test (error "no fst") (error "no snd");
    [%expect {| (Error ("no fst" "no snd")) |}]
  ;;

  let map3 = A.map3

  let%expect_test _ =
    let test x y z =
      print_s [%sexp (map3 x y z ~f:(fun a b c -> a ^ b ^ c) : string Or_error.t)]
    in
    test (Ok "o") (Ok "k") (Ok "ay");
    [%expect {| (Ok okay) |}];
    test (error "not okay") (Ok "k") (Ok "ay");
    [%expect {| (Error "not okay") |}];
    test (Ok "o") (error "not okay") (Ok "ay");
    [%expect {| (Error "not okay") |}];
    test (Ok "o") (Ok "k") (error "not okay");
    [%expect {| (Error "not okay") |}];
    test (error "no 1st") (error "no 2nd") (error "no 3rd");
    [%expect {| (Error ("no 1st" "no 2nd" "no 3rd")) |}]
  ;;

  let all = A.all

  let%expect_test _ =
    let test list = print_s [%sexp (all list : string list Or_error.t)] in
    test [];
    [%expect {| (Ok ()) |}];
    test [ Ok "okay" ];
    [%expect {| (Ok (okay)) |}];
    test [ Ok "o"; Ok "kay" ];
    [%expect {| (Ok (o kay)) |}];
    test [ Ok "o"; Ok "k"; Ok "ay" ];
    [%expect {| (Ok (o k ay)) |}];
    test [ error "oh no!" ];
    [%expect {| (Error "oh no!") |}];
    test [ error "oh no!"; Ok "okay" ];
    [%expect {| (Error "oh no!") |}];
    test [ Ok "okay"; error "oh no!" ];
    [%expect {| (Error "oh no!") |}];
    test [ error "oh no!"; Ok "o"; Ok "kay" ];
    [%expect {| (Error "oh no!") |}];
    test [ Ok "o"; error "oh no!"; Ok "aay" ];
    [%expect {| (Error "oh no!") |}];
    test [ Ok "o"; Ok "kay"; error "oh no!" ];
    [%expect {| (Error "oh no!") |}];
    test [ error "oh"; error "no"; error "!" ];
    [%expect {| (Error (oh no !)) |}]
  ;;

  let all_unit = A.all_unit

  let%expect_test _ =
    let test list = print_s [%sexp (all_unit list : unit Or_error.t)] in
    test [];
    [%expect {| (Ok ()) |}];
    test [ Ok () ];
    [%expect {| (Ok ()) |}];
    test [ Ok (); Ok () ];
    [%expect {| (Ok ()) |}];
    test [ Ok (); Ok (); Ok () ];
    [%expect {| (Ok ()) |}];
    test [ error "oh no!" ];
    [%expect {| (Error "oh no!") |}];
    test [ error "oh no!"; Ok () ];
    [%expect {| (Error "oh no!") |}];
    test [ Ok (); error "oh no!" ];
    [%expect {| (Error "oh no!") |}];
    test [ error "oh no!"; Ok (); Ok () ];
    [%expect {| (Error "oh no!") |}];
    test [ Ok (); error "oh no!"; Ok () ];
    [%expect {| (Error "oh no!") |}];
    test [ Ok (); Ok (); error "oh no!" ];
    [%expect {| (Error "oh no!") |}];
    test [ error "oh"; error "no"; error "!" ];
    [%expect {| (Error (oh no !)) |}]
  ;;

  module Applicative_infix = A.Applicative_infix
end

let%test_module "Make" =
  (module Test_applicative_s (Applicative.Make (struct
    type 'a t = 'a Or_error.t

    let return = Or_error.return
    let apply = Or_error.apply
    let map = `Define_using_apply
  end)))
;;

let%test_module "Make" =
  (module Test_applicative_s (Applicative.Make_using_map2 (struct
    type 'a t = 'a Or_error.t

    let return = Or_error.return
    let map2 = Or_error.map2
    let map = `Define_using_map2
  end)))
;;

let%test_module "Make" =
  (module Test_applicative_s (Applicative.Make_using_map2_local (struct
    type 'a t = 'a Or_error.t

    let return x = Ok x
    let map2 = Or_error.map2
    let map = `Define_using_map2
  end)))
;;

(* While law-abiding applicatives shouldn't be relying functions being called
   the minimal number of times, it is good for performance that things be this
   way. For many applicatives this will not matter very much, but for others,
   like Bonsai, it is a little more significant, since extra calls construct
   more Incremental nodes, yielding more strain on the Incremental stabilizer.

   The point is that we should not assume that the input applicative instance
   can be frivolous in creating nodes in the applicative call-tree.
*)
let%expect_test _ =
  let module A = struct
    type 'a t =
      | Other of string
      | Return : 'a -> 'a t
      | Map : ('a -> 'b) * 'a t -> 'b t
      | Map2 : ('a -> 'b -> 'c) * 'a t * 'b t -> 'c t

    include Applicative.Make_using_map2 (struct
      type nonrec 'a t = 'a t

      let return x = Return x
      let map2 a b ~f = Map2 (f, a, b)
      let map = `Custom (fun a ~f -> Map (f, a))
    end)

    let rec sexp_of_t : type a. a t -> Sexp.t = function
      | Other x -> Atom x
      | Return _ -> Atom "Return"
      | Map (_, a) -> List [ Atom "Map"; sexp_of_t a ]
      | Map2 (_, a, b) -> List [ Atom "Map2"; sexp_of_t a; sexp_of_t b ]
    ;;
  end
  in
  let open A in
  let test x = print_s [%sexp (x : A.t)] in
  let a, b, c, d = Other "A", Other "B", Other "C", Other "D" in
  test (map2 a b ~f:(fun a b -> a, b));
  [%expect {| (Map2 A B) |}];
  test (both a b);
  [%expect {| (Map2 A B) |}];
  test (all_unit [ a; b; c; d ]);
  [%expect {| (Map2 (Map2 (Map2 (Map2 Return A) B) C) D) |}];
  test (a *> b);
  [%expect {| (Map2 A B) |}]
;;

(* These functors serve only to check that the signatures for various Foo and Foo2 module
   types don't drift apart over time. *)
module _ = struct
  open Applicative

  (* Applicative_infix to Applicative_infix2 *)

  module _ (X : Applicative_infix) : Applicative_infix2 with type ('a, 'e) t = 'a X.t =
  struct
    include X

    type ('a, 'e) t = 'a X.t
  end

  (* Applicative_infix2 to Applicative_infix *)
  module _ (X : Applicative_infix2) : Applicative_infix with type 'a t = ('a, unit) X.t =
  struct
    include X

    type 'a t = ('a, unit) X.t
  end

  (* Applicative_infix2 to Applicative_infix3 *)
  module _ (X : Applicative_infix2) :
    Applicative_infix3 with type ('a, 'd, 'e) t = ('a, 'd) X.t = struct
    include X

    type ('a, 'd, 'e) t = ('a, 'd) X.t
  end

  (* Applicative_infix3 to Applicative_infix2 *)
  module _ (X : Applicative_infix3) :
    Applicative_infix2 with type ('a, 'd) t = ('a, 'd, unit) X.t = struct
    include X

    type ('a, 'd) t = ('a, 'd, unit) X.t
  end

  (* Let_syntax to Let_syntax2 *)
  module _ (X : Let_syntax) : Let_syntax2 with type ('a, 'e) t = 'a X.t = struct
    include X

    type ('a, 'e) t = 'a X.t
  end

  (* Let_syntax2 to Let_syntax *)
  module _ (X : Let_syntax2) : Let_syntax with type 'a t = ('a, unit) X.t = struct
    include X

    type 'a t = ('a, unit) X.t
  end

  (* Let_syntax2 to Let_syntax3 *)
  module _ (X : Let_syntax2) : Let_syntax3 with type ('a, 'd, 'e) t = ('a, 'd) X.t =
  struct
    include X

    type ('a, 'd, 'e) t = ('a, 'd) X.t
  end

  (* Let_syntax3 to Let_syntax2 *)
  module _ (X : Let_syntax3) : Let_syntax2 with type ('a, 'd) t = ('a, 'd, unit) X.t =
  struct
    include X

    type ('a, 'd) t = ('a, 'd, unit) X.t
  end
end
