Highly, terribly dangerous posts. Misuse of this profile can invite eggbug to trounce upon your data and then laugh in your face. You don't want this profile. Really.



I've spend a lot of time trying to do this now, but I'm fairly convinced it's just not possible with modules.

Superficially, it seems like Trees That Grow would be an amazing fit for functors!

The aim of TTG is to be able to extend an annotate a syntax tree without having to duplicate the entire definition. In Haskell, this is achieved by parameterizing the syntax tree over the current pass and selecting the annotations with type families.

In OCaml, Functors sound like a great solution for this! One could just parameterize the syntax module over a module containing every single extension type like this

(* Syntax.ml *)

module Make(sig
    type name

    type var_ext
    type app_ext
    type lambda_ext
end) = struct
    type expr =
        | Var of var_ext * name
        | App of app_ext * expr * expr
        | Lambda of lamba_ext * name * expr
end

module Parsed = Make(struct
    type name = string
    type var_ext = unit
    type app_ext = unit
    type lambda_ext = unit
end)

module Renamed = Make(struct
    type name = some_better_name_representation
    type var_ext = unit
    type app_ext = unit
    type lambda_ext = unit
end)

Looks great right? This is what I did until now actually! It's just that... this doesn't work.

Let's say your syntax module looks like this and now you want to add a type checker to your language and annotate every expression with its type.

So you extend your syntax module with types and add a new pass

module Make (...) = struct
    type expr = ...

    type ty = 
        | Fun of ty * ty
        | Var of name
        | Forall of name * ty
end

...

module Typed = Make(struct
    type name = some_better_name_representation
    type var_ext = ???
    type app_ext = ???
    type lambda_ext = ???
end)

How do you refer to types now? The type you want is Typed.ty, but you cannot refer to the module Typed while defining it.

Mutually recursive modules sound like an obvious solution

module rec TypedArg = struct
    type name = some_better_name_representation
    type var_ext = Typed.ty
    type app_ext = Typed.ty
    type lambda_ext = Typed.ty
end
and Typed = Make(TypedArg)

But this doesn't compile. Recursive modules only compile with explicit module signatures and in order to export every single constructor from the module, you would need to write out the entire type definition, defeating the point of using TTG to avoid duplicate definitions in the first place.

module rec TypedArg : sig ... end = struct
    ...
end
and Typed : sig
    type expr = ...
    type ty = ...
end = Make(TypedArg)

Reconstructing the module type with module type of doesn't work either, since the resulting type depends on the functor argument and there is no way to say "give me the module type of this functor when applied to this module".

I've given up now, I don't think this is possible with modules. I'm going to try writing a PPX rewriter (kind of like TemplateHaskell) instead to generate the definitions. This doesn't allow me to write pass-agnostic functions, but I'm content if I get this working at all.


You must log in to comment.

in reply to @prophet's post:

Reconstructing the module type with module type of doesn't work either, since the resulting type depends on the functor argument and there is no way to say "give me the module type of this functor when applied to this module".

Actually there is, that’s just module type of Make (I) but sadly it’s not accepted by the type-checker in the context of mutually recursive modules. (it is accepted otherwise)

I wish something like that would be accepted by the type-checker:

module rec TypedArg : sig
  type name = string
  type var_ext = Typed.ty
  type app_ext = Typed.ty
  type lambda_ext = Typed.ty
end = TypedArg
and Typed : module type of Make (TypedArg) = Typed

but I suppose it complicates the type-checker too much (you now need to check that the generated types are not cyclic and that sounds quite tricky and costly)

If you did it by replacing the recursion with an explicit staging:

module Make (M : sig type name end) = struct
  type ty = Fun of ty * ty | Var of M.name
end

module TypedArg = struct
  module Inner = struct
    type name = ...
  end
  include Inner
  module Typed = Make (Inner)
  type var_ext = Typed.ty
end

is the problem here that it doesn't allow name to depend on Typed.ty?

Oh interesting! I think this works for this example and it would even work for the syntax tree in my real project, but it's going to break down pretty soon once I add view patterns, since these introduce mutual recursion between expressions and patterns