Id monad evaluation returning abstact type instead of int result

Hi, I am having this Id monad:

module type MONAD_SIG =
  sig
    type 'a t
    val return : 'a -> 'a t
    val bind : 'a t -> ('a -> 'b t) -> 'b t
    val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
  end

module IdMonad : MONAD_SIG =
  struct
    type 'a t = Id of 'a
    let return v = Id v
    let bind (Id v) f = f v
    let (>>=) = bind (* always the case *)
  end
module Term = struct
  type term = Const of int | Div of term * term | Add of term * term | Mlt of term * term
  let div_answer = Div(Div(Const 1972, Const 2), Const 23)
  let add_answer = Add(Add(Const 1972, Const 2), Const 23)
  let mlt_answer = Mlt(Mlt(Const 1972, Const 2), Const 23)
  let mix_answer = Mlt(mlt_answer, add_answer)
  let error = Div(Const 1, Const 0)
end
module type MONAD_SIG_ARITHMETIC =
  sig
  include MONAD_SIG
  val divM : int -> int -> int t
  val addM : int -> int -> int t
  val mltM : int -> int -> int t
  end
module IdMonadArithmetic : MONAD_SIG_ARITHMETIC = struct
  include IdMonad
  let divM a b = return (a / b)
  let addM a b = return (a + b)
  let mltM a b = return (a * b)
end
module MakeEvalMonad(Q : MONAD_SIG_ARITHMETIC) = struct
  include Term
  open Q
  let evalM t =
    let rec evalMrec t =
      match t with
      | Const c -> return c
      | Div(a,b)-> evalMrec a >>= (fun n -> evalMrec b >>= (fun d -> Q.divM n d))
      | Add(a,b)-> evalMrec a >>= (fun n -> evalMrec b >>= (fun d -> Q.addM n d))
      | Mlt(a,b)-> evalMrec a >>= (fun n -> evalMrec b >>= (fun d -> Q.mltM n d))
      in return t >>= evalMrec
end
module EvalIdMonad = MakeEvalMonad(IdMonadArithmetic)
(* open EvalIdMonad *)
let _ = EvalIdMonad.evalM EvalIdMonad.mlt_answer

And I am using functor for MakeEvalMonad, however, when I am trying to run let _ = EvalIdMonad.evalM EvalIdMonad.mlt_answer, I get this - : int IdMonadArithmetic.t = <abstr> instead of Id 90712, what is the problem? (Thanks!)

This is hiding the type definition because MONAD_SIG_ARITHMETIC exposes only an abstact type 'a t. You can remove the module type annotation to get the expected result. The compiler automatically checks the module type conformance.

You can also simplify the code by defining:

module IdMonad = struct
  type 'a t = 'a
  ...

And so on.

I still get the same result: - : int IdMonadArithmetic.t = <abstr>.
What I did was just doing this module IdMonadArithmetic = struct include IdMonad .... I did it before asking because that’s what I thought.

I even tried to simply the code like below, and it still gives: - : int IdMonad.t = <abstr>

module Term = struct
  type term = Const of int | Div of term * term | Add of term * term | Mlt of term * term
  let div_answer = Div(Div(Const 1972, Const 2), Const 23)
  let add_answer = Add(Add(Const 1972, Const 2), Const 23)
  let mlt_answer = Mlt(Mlt(Const 1972, Const 2), Const 23)
  let mix_answer = Mlt(mlt_answer, add_answer)
  let error = Div(Const 1, Const 0)
end

module type MONAD_SIG =
  sig
    type 'a t
    val return : 'a -> 'a t
    val bind : 'a t -> ('a -> 'b t) -> 'b t
    val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
    val divM : int -> int -> int t
    val addM : int -> int -> int t
    val mltM : int -> int -> int t
  end

module IdMonad : MONAD_SIG =
  struct
    type 'a t = Id of 'a
    let return v = Id v
    let bind (Id v) f = f v
    let (>>=) = bind (* always the case *)
    let divM a b = return (a / b)
    let addM a b = return (a + b)
    let mltM a b = return (a * b)
  end

module MakeEvalMonad(Q : MONAD_SIG) = struct
  include Term
  open Q
  let evalM t =
    let rec evalMrec t =
      match t with
      | Const c -> return c
      | Div(a,b)-> evalMrec a >>= (fun n -> evalMrec b >>= (fun d -> divM n d))
      | Add(a,b)-> evalMrec a >>= (fun n -> evalMrec b >>= (fun d -> addM n d))
      | Mlt(a,b)-> evalMrec a >>= (fun n -> evalMrec b >>= (fun d -> mltM n d))
      in return t >>= evalMrec
end
module EvalIdMonad = MakeEvalMonad(IdMonad)
(* open EvalIdMonad *)
let _ = EvalIdMonad.evalM EvalIdMonad.mlt_answer

I just ran your original code sample after removing two module type annotations and got the desired result:

utop # let ans = EvalIdMonad.evalM EvalIdMonad.mlt_answer;;
val ans : int IdMonadArithmetic.t = IdMonadArithmetic.Id 90712

Oh I see, which two module annotations please?

There are only two modules in your original code which have module type annotations. They look like this: module M : T = ...

It worked now, when I removed the : MONAD_SIG from module IdMonad : MONAD_SIG = , but why is that? I mean, even in the simplified code where an annotation shouldn’t have been a problem.

Same reason as my original reply. Basically the module type annotation hides the internals of the module, including the type definition, and only allows an abstract view of the type from external to the module.

I see, I get it now. Thanks

1 Like

I’ve noticed time and time again that this kind of sealing with signatures often just gets in my way. I have to recover transparency later with something like:

module IDMonad : Monad with type 'a t = 'a
  = struct ... end

which gets repetitive and has its own drawbacks. Or by removing signatures like yawaramin did.

I feel like it’s better to be specific only in what you accept… That is, use signature declaration only for functor arguments, and leave modules open. If you want the assurance of interface-conformance, I’ve resorted to do that in a testing file:

module _ = (ID : Monad)

IDK if this is the most ideal, or if I’m just inexperienced with software engineering practices and committing atrocities, but it has simplified my code a bit at the module language and lowered the level of verbosity.

Very nice, I think it must deliver a good result for those who are into long projects in Ocaml.