type ('a, 'error) t = ('a, 'error) result =
  | Ok of 'a
  | Error of 'error

let ok x = Ok x

let return = ok

let is_ok = function
  | Ok _ -> true
  | Error _ -> false

let is_error = function
  | Ok _ -> false
  | Error _ -> true

let ok_exn = function
  | Ok x -> x
  | Error e -> raise e

let try_with f =
  match f () with
  | s -> Ok s
  | exception e -> Error e

let bind t ~f =
  match t with
  | Ok x -> f x
  | Error _ as t -> t

let ( >>= ) x f = bind x ~f

let map x ~f =
  match x with
  | Ok x -> Ok (f x)
  | Error _ as x -> x

let map_error x ~f =
  match x with
  | Ok _ as res -> res
  | Error x -> Error (f x)

let iter t ~f =
  match t with
  | Ok x -> f x
  | Error _ -> ()

let to_option = function
  | Ok p -> Some p
  | Error _ -> None

let errorf fmt = Printf.ksprintf (fun x -> Error x) fmt

let both a b =
  match a with
  | Error e -> Error e
  | Ok a -> (
    match b with
    | Error e -> Error e
    | Ok b -> Ok (a, b) )

module O = struct
  let ( >>= ) t f = bind t ~f

  let ( >>| ) t f = map t ~f

  let ( let* ) = ( >>= )

  let ( let+ ) = ( >>| )

  let ( and+ ) = both
end

open O

type ('a, 'error) result = ('a, 'error) t

module List = struct
  let map t ~f =
    let rec loop acc = function
      | [] -> Ok (List.rev acc)
      | x :: xs -> f x >>= fun x -> loop (x :: acc) xs
    in
    loop [] t

  let all =
    let rec loop acc = function
      | [] -> Ok (List.rev acc)
      | t :: l -> t >>= fun x -> loop (x :: acc) l
    in
    fun l -> loop [] l

  let concat_map =
    let rec loop f acc = function
      | [] -> Ok (List.rev acc)
      | x :: l -> f x >>= fun y -> loop f (List.rev_append y acc) l
    in
    fun l ~f -> loop f [] l

  let rec iter t ~f =
    match t with
    | [] -> Ok ()
    | x :: xs -> f x >>= fun () -> iter xs ~f

  let rec fold_left t ~f ~init =
    match t with
    | [] -> Ok init
    | x :: xs -> f init x >>= fun init -> fold_left xs ~f ~init
end

let hash h1 h2 t =
  Stdlib.Hashtbl.hash
    ( match t with
    | Ok s -> h1 s
    | Error e -> h2 e )

let equal e1 e2 x y =
  match (x, y) with
  | Ok x, Ok y -> e1 x y
  | Error x, Error y -> e2 x y
  | _, _ -> false

module Option = struct
  let iter t ~f =
    match t with
    | None -> Ok ()
    | Some x -> x >>= f
end

let to_dyn ok err = function
  | Ok e -> Dyn.Encoder.constr "Ok" [ ok e ]
  | Error e -> Dyn.Encoder.constr "Error" [ err e ]
