Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monad Transformer Stack #1354

Closed
addap opened this issue Oct 10, 2021 · 4 comments
Closed

Monad Transformer Stack #1354

addap opened this issue Oct 10, 2021 · 4 comments

Comments

@addap
Copy link
Contributor

addap commented Oct 10, 2021

Hi,

I was wondering how to compose multiple monad transformers from your transformer library.
I'm using version 2.3.0 from opam.

The naive approach does not work

open Monads.Std
module R = Monad.Reader.Make(struct type t = string end)(Monad.Ident)
module SR = Monad.State.Make(struct type t = string end)(R)

because R is missing a definition for type t, so it does not conform to the Monad signature.

I saw in ogre.ml that you can hack around this by defining t in a new module

bap/lib/ogre/ogre.ml

Lines 808 to 813 in 51b4988

module EM = struct
type 'a t = 'a Or_error.t B.t
include Monad.Result.Error.Make(B)
end
module M = Monad.State.Make(Doc)(EM)

But that is not mentioned in the docs as far as I can tell. So is that the recommended way to build a monad transformer stack using this library?

@ivg
Copy link
Member

ivg commented Oct 12, 2021

This is the recommended way. You can also leverage the type-building functors, Monad.<M>.T or Monad.<M>.T2., e.g.,

module M = Monad.State.Make(Doc)(struct
    include Monad.Result.Error.T(B)         (* adds types *)
    include Monad.Result.Error.Make(B)      (* adds functions and modules *)
  end)

This is a rather common convention in OCaml, especially within the Janestreet-style libraries. The motivation is to enable composability between modules that are generated by functors. Basically, each functor implements some trait and the final module is composed of several includes, e.g.,

module Point = struct
   type t = ...
   include Base.Comparable(struct ... end)
   include Stringable(struct ... end)

Since before some, rather recent, version of OCaml, it wasn't possible to include a module that defines a type into a structure that already has that type (even it is equal) we had to remove types from the traits. It is no longer a problem in modern OCaml, at least in 4.09.0 I can safely compose two modules with intersecting types, but the convention is still there. At least for backward compatibility with the older versions of OCaml.

We can probably add a functor Make_with_types (or something like this) for those who are using modern versions of OCaml.

@ivg
Copy link
Member

ivg commented Oct 15, 2021

@addap, are you satisfied with an answer? Maybe you have any suggestions on how we can improve it? Starting from documentation and ending in implementing, MakeT for each monad that will make compositions easier? PRs are very welcome!

@addap
Copy link
Contributor Author

addap commented Oct 17, 2021

Hi @ivg,
thanks for the explanation. The type-building functors are really convenient for this.
I'm currently finishing up a project in October but after that I'd be happy to do a PR.
In this project I initially wanted to use the monad library but did not because I was new to OCaml and did not understand the module system enough to do the above. So putting it in the docs would be helpful for other newcomers imo.

Since the MakeT functor is probably the same everywhere I'm wondering if functors can be parameterized by functors in OCaml?
So one could write

module MakeMakeT (TYPE_F: Monad -> sig type t end) (F: Monad -> S) (B: Monad) : Monad = struct
    include TYPE_F(B)
    include F(B)
end
...
MakeT(B) = MakeMakeT(T)(Make)(B)

but I guess some T functors have type definitions other than t which we would not want to lose and this approach might be too complicated anyways.

@ivg
Copy link
Member

ivg commented Oct 19, 2021

Yes, that would work and this is indeed how I would implement it. The only minor caveat is that we have two kinds of monads, unary 'a t and binary ('a,'s) t, so we will end up having two functor-generators (which I probably won't even publish to reduce confusion) and two sets of functors, MakeT and MakeT2 for unary and binary monads correspondingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants