Extending recursive modules - module

So when you define the structure of a module, it's possible to extend another module off of it:
module Base = struct
type t = Name of string
end
module Child = struct
include Base
end
Child.Name "test"
(* - : Child.t = Child.Name "test" *)
However, when working with recursive modules using recursive signatures, I run into issues when I try to extend a module:
module rec Base : sig
type t = | Name of string
end = Base
and Child : sig
include Base
end = Child
When I do this, I get an error saying:
Error: Unbound module type Base
Can you not extend modules when working with this recursive module trick? Am I misunderstanding something or doing something wrong?

It seems to me your problem is that Base is a module, not a module type. When including in a sig ... end construct you need a type. When including in a struct ... end construct you need a module. That's why the first example works and the second one doesn't.
If I change Base to module type of Base I get this error:
Error: Illegal recursive module reference
So I suspect this particular (somewhat strange) type of recursive definition isn't supported.
If you define the module type separately, you can make it work:
module type BASE = sig type t = Name of string end
module rec Base : BASE = Base
and Child : sig
include BASE
end = Child

Jeffrey Scofield already gave a good answer. I'd just like to add that include is just syntactic sugar. So if it is include which is causing you trouble, a solution might be to expand it. In the first of your examples that would lead to
module Base = struct
type t = Name of string
end
module Child = struct
type t = Name of string
end
Apparently, your examples are simplified versions of what you really want to do. As shown, there is no need to use rec at all. So I can only guess how much recursion you really need. According to Jeffrey Scofield's answer, the combination of rec and include is problematic. Possibly, getting rid of include suffices for you.

Related

In OCaml, what does aliasing a module do exactly?

In OCaml, to bring another module in scope you can use open. But what about code like this:
module A = struct
include B.C
module D = B.E
end
Does this create an entirely new module called A that has nothing to do with the modules created by B? Or are the types in B equivalent to this new structure and can a type in A.t can be used interchangeably with a type in B.C.t for example?
Especially, comparing to Rust I believe this is very different from writing something like
pub mod a {
pub use b::c::*;
pub use b::e as d;
}
Yes, module A = struct include B.C end creates an entirely new module and exports all definitions from B.C. All abstract types and data types that are imported from B.C are explicitly related to that module.
In other words, suppose you have
module Inner = struct
type imp = Foo
type t = int
end
so when we import Inner we can access the Inner definitions,
module A = struct
include Inner
let x : imp = Foo
let 1 : t = 1
end
and the Foo constructor in A belongs to the same type as the Foo constructor in the Inner module so that the following typechecks,
A.x = Inner.Foo
In other words, include is not a mere copy-paste, but something like this,
module A = struct
(* include Inner expands to *)
type imp = Inner.imp = Foo
type t = Inner.t = int
end
This operation of preserving type equalities is formally called strengthening and always applied when OCaml infers module type. In other words, the type system never forgets the type sharing constraints and the only way to remove them is to explicitly specify the module type that doesn't expose the sharing constraints (or use the module type of construct, see below).
For example, if we will define a module type
module type S = sig
type imp = Foo
type t = int
end
then
module A = struct
include (Inner : S)
end
will generate a new type foo, so A.Foo = Inner.Foo will no longer type check. The same could be achieved with the module type of construct that explicitly disables module type strengthening,
module A = struct
include (Inner : module type of Inner)
end
will again produce A.Foo that is distinct from Inner.Foo. Note that type t will be still compatible in all implementation as it is a manifest type and A.t is equal to Inner.t not via a sharing constraint but since both are equal to int.
Now, you might probably have the question, what is the difference between,
module A = Inner
and
module A = struct include Inner end
The answer is simple. Semantically they are equivalent. Moreover, the former is not a module alias as you might think. Both are module definitions. And both will define a new module A with exactly the same module type.
A module alias is a feature that exists on the (module) type level, i.e., in the signatures, e.g.,
module Main : sig
module A = Inner (* now this is the module alias *)
end = struct
module A = Inner
end
So what the module alias is saying, on the module level, is that A is not only has the same type as Inner but it is exactly the Inner module. Which opens to the compiler and toolchain a few opportunities. For example, the compiler may eschew module copying as well as enable module packing.
But all this has nothing to do with the observed semantics and especially with the typing. If we will forget about the explicit equality (that is again used mostly for more optimal module packing, e.g., in dune) then the following definition of the module A
module Main = struct
module A = Inner
end
is exactly the same as the above that was using the module aliasing. Anything that was typed with the previous definition will be typed with the new definition (modulo module type aliases). It is as strong. And the following is as strong,
module Main = struct
module A = struct include Inner end
end
and even the following,
module Main : sig
module A : sig
type imp = Impl.imp = Foo
type t = Impl.t = int
end
end = struct
module A = Impl
end

How to add old HR INCLUDE into local class?

So I need to use the INCLUDES rpcblo00 and rpcbdt00 to get the type of infotype change (create, update, delete). Beforehand I used a subroutine that had no problem with the includes, but I cannot get them into a class for the life of me.
If I try to put the include into the method as described here (it's even about the same HR include), I get the following error (because of the minus in lo-key):
Syntax error: Names may only consist of the characters "A-Z", "0-9"
and "_". In addition, they may not begin with a number.
minimal reproducible example:
CLASS lcl_infotypaenderungen DEFINITION.
PUBLIC SECTION.
TYPES: tty_aenderungs_operationen TYPE STANDARD TABLE OF pc403.
METHODS:
constructor
IMPORTING is_aenderungs_kopf TYPE pldoc_key,
get_aenderungs_operationen
RETURNING value(rt_aenderungs_operationen) TYPE tty_aenderungs_operationen.
PRIVATE SECTION.
DATA: s_aenderungs_kopf TYPE pldoc_key,
t_aenderungs_operationen TYPE tty_aenderungs_operationen.
METHODS:
select_aenderungs_operationen.
ENDCLASS. "lcl_infotypaenderungen DEFINITION
*----------------------------------------------------------------------*
TYPE-POOLS: abap.
DATA: lo_infotypaenderungen TYPE REF TO lcl_infotypaenderungen,
lv_fehler TYPE sy-subrc,
lt_log_kopf TYPE pldoc_key_tab WITH HEADER LINE,
lt_log_felder TYPE TABLE OF hrinftylog_fields,
lt_infotyp_vorher TYPE prelp_tab,
lt_infotyp_nachher TYPE prelp_tab,
lt_aenderungs_operationen TYPE STANDARD TABLE OF pc403.
FIELD-SYMBOLS: <log_kopfzeile> TYPE pldoc_key.
*----------------------------------------------------------------------*
CALL FUNCTION 'HR_INFOTYPE_LOG_GET_LIST'
EXPORTING
tclas = 'A'
begda = '20190315'
endda = '20190315'
IMPORTING
subrc = lv_fehler
TABLES
infty_logg_key_tab = lt_log_kopf.
CLEAR lv_fehler.
SORT lt_log_kopf DESCENDING BY infty bdate btime pernr.
LOOP AT lt_log_kopf ASSIGNING <log_kopfzeile>.
CALL FUNCTION 'HR_INFOTYPE_LOG_GET_DETAIL'
EXPORTING
logged_infotype = <log_kopfzeile>
IMPORTING
subrc = lv_fehler
TABLES
infty_tab_before = lt_infotyp_vorher
infty_tab_after = lt_infotyp_nachher
fields = lt_log_felder.
CREATE OBJECT lo_infotypaenderungen
EXPORTING
is_aenderungs_kopf = <log_kopfzeile>.
REFRESH lt_aenderungs_operationen.
lt_aenderungs_operationen = lo_infotypaenderungen->get_aenderungs_operationen( ).
ENDLOOP.
*----------------------------------------------------------------------*
CLASS lcl_infotypaenderungen IMPLEMENTATION.
METHOD constructor.
me->s_aenderungs_kopf = is_aenderungs_kopf.
me->select_aenderungs_operationen( ).
ENDMETHOD. "constructor
METHOD select_aenderungs_operationen.
INCLUDE rpcblo00. """ <---
INCLUDE rpcbdt00. """ <---
lo-key-tclas = me->s_aenderungs_kopf-tclas.
lo-key-pernr = me->s_aenderungs_kopf-pernr.
lo-key-infty = me->s_aenderungs_kopf-infty.
lo-key-bdate = me->s_aenderungs_kopf-bdate.
lo-key-btime = me->s_aenderungs_kopf-btime.
lo-key-seqnr = me->s_aenderungs_kopf-seqnr.
IMPORT header TO me->t_aenderungs_operationen FROM DATABASE pcl4(la) ID lo-key.
ENDMETHOD. "select_aenderungs_operationen
METHOD get_aenderungs_operationen.
rt_aenderungs_operationen = me->t_aenderungs_operationen.
ENDMETHOD. "get_aenderungs_operationen
ENDCLASS. "lcl_infotypaenderungen IMPLEMENTATION
Anyone know a good solution? Thanks in advance
Edit: The includes have some declarations and a makro reading from a data cluster. Of course I could just put those directly into the method, but I would like to avoid that (for now I did that).
Alternatively, does someone know of a different way to get the change operation per infotype line?
If you use your class as a local one then the only way to use these includes is to put them at the very beginning of the program. The downside is of course that the variables there become global but unfortunately there is no other way to do that and for sure not if you want to use a global class after all (not sure if your minimal working example is just simplified to use a local class instead of global or not).
REPORT ZZZ.
INCLUDE rpcblo00. """ <---
INCLUDE rpcbdt00. """ <---
CLASS lcl_infotypaenderungen DEFINITION.
" ...
Thanks to Jagger I can make it work with a local class, but in case anyone later wonders how you need to change the include code to be able to use it in a global method, you basically just need to get rid of INCLUDE STRUCTURE declarations and exchange tables with a header line.
So
DATA BEGIN OF LO-KEY.
INCLUDE STRUCTURE PC400.
DATA END OF LO-KEY.
becomes
DATA: lo_key TYPE pc400.
And
DATA BEGIN OF BELEGE_00 OCCURS 100.
DATA:
SPLKZ(01) TYPE X,
FIELD(10) TYPE C,
FTYPE(04) TYPE C,
FLENG(03) TYPE N,
DECIM(02) TYPE N,
OLDDT(50) TYPE C,
NEWDT(50) TYPE C.
DATA END OF BELEGE_00.
becomes
TYPES: BEGIN OF ty_belege,
splkz(01) TYPE x,
field(10) TYPE c,
ftype(04) TYPE c,
fleng(03) TYPE n,
decim(02) TYPE n,
olddt(50) TYPE c,
newdt(50) TYPE c,
END OF ty_belege.
DATA: belege_00 TYPE STANDARD TABLE OF ty_belege.
The macro can stay the same (or I guess you could rewrite it).

How to get a module type from an interface?

I would like to have my own implementation of an existing module but to keep a compatible interface with the existing module. I don't have a module type for the existing module, only an interface. So I can't use include Original_module in my interface. Is there a way to get a module type from an interface?
An example could be with the List module from the stdlib. I create a My_list module with exactly the same signature than List. I could copy list.mli to my_list.mli, but it does not seem very nice.
In some cases, you should use
include module type of struct include M end (* I call it OCaml keyword mantra *)
rather than
include module type of M
since the latter drops the equalities of data types with their originals defined in M.
The difference can be observed by ocamlc -i xxx.mli:
include module type of struct include Complex end
has the following type definition:
type t = Complex.t = { re : float; im : float; }
which means t is an alias of the original Complex.t.
On the other hand,
include module type of Complex
has
type t = { re : float; im : float; }
Without the relation with Complex.t, it becomes a different type from Complex.t: you cannot mix code using the original module and your extended version without the include hack. This is not what you want usually.
You can look at RWO : if you want to include the type of a module (like List.mli) in another mli file :
include (module type of List)

OCaml This function is applied to too many arguments

This is just a simple program I wrote to try to get a better understanding about modules. I'm trying to call the toS function with Id("a",Int) but it seems like I can write a type ast like this. What may be the problem?
module Typ =
struct
type typ = Int | Bool
end
module Symbol =
struct
type t = string
end
module Ast =
struct
type ast = Const of int * Typ.typ | Id of Symbol.t * Typ.typ
let rec toS ast = match ast with Id(a,b) -> "a"
|_->"b"
end
Ast.toS Id("a",Int)
You are getting an error because you did not surround your type constructor with parens in the function application. However, you must also reference type constructors by fully qualified names outside the module they're defined in. I.e.
Ast.toS (Ast.Id("a",Typ.Int))
Alternatively, you can open the modules. But this is considered bad practice. I.e.
open Id
open Typ
Ast.toS (Id("a",Int))

Understanding functors in OCaml

I'm quite stuck with the following functor problem in OCaml. I paste some of the code just to let you understand. Basically
I defined these two modules in pctl.ml:
module type ProbPA = sig
include Hashtbl.HashedType
val next: t -> (t * float) list
val print: t -> float -> unit
end
module type M = sig
type s
val set_error: float -> unit
val check: s -> formula -> bool
val check_path: s -> path_formula -> float
val check_suite: s -> suite -> unit
end
and the following functor:
module Make(P: ProbPA): (M with type s = P.t) = struct
type s = P.t
(* implementation *)
end
Then to actually use these modules I defined a new module directly in a file called prism.ml:
type state = value array
type t = state
type value =
| VBOOL of bool
| VINT of int
| VFLOAT of float
| VUNSET
(* all the functions required *)
From a third source (formulas.ml) I used the functor with Prism module:
module PrismPctl = Pctl.Make(Prism)
open PrismPctl
And finally from main.ml
open Formulas.PrismPctl
(* code to prepare the object *)
PrismPctl.check_suite s.sys_state suite (* error here *)
and compiles gives the following error
Error: This expression has type Prism.state = Prism.value array
but an expression was expected of type Formulas.PrismPctl.s
From what I can understand there a sort of bad aliasing of the names, they are the same (since value array is the type defined as t and it's used M with type s = P.t in the functor) but the type checker doesn't consider them the same.
I really don't understand where is the problem, can anyone help me?
Thanks in advance
(You post non-compilable code. That's a bad idea because it may make it harder for people to help you, and because reducing your problem down to a simple example is sometimes enough to solve it. But I think I see your difficulty anyway.)
Inside formulas.ml, Ocaml can see that PrismPctl.s = Pctl.Make(Prism).t = Prism.t; the first equality is from the definition of PrismPctl, and the second equality is from the signature of Pctl.Make (specifically the with type s = P.t bit).
If you don't write an mli file for Formulas, your code should compile. So the problem must be that the .mli file you wrote doesn't mention the right equality. You don't show your .mli files (you should, they're part of the problem), but presumably you wrote
module PrismPctl : Pctl.M
That's not enough: when the compiler compiles main.ml, it won't know anything about PrismPctl that's not specified in formulas.mli. You need to specify either
module PrismPctl : Pctl.M with type s = Prism.t
or, assuming you included with type s = P.t in the signature of Make in pctl.mli
module PrismPctl : Pctl.M with type s = Pctl.Make(Prism).s
This is a problem I ran into as well when learning more about these. When you create the functor you expose the signature of the functor, in this case M. It contains an abstract type s, parameterized by the functor, and anything more specific is not exposed to the outside. Thus, accessing any record element of s (as in sys_state) will result in a type error, as you've encountered.
The rest looks alright. It is definitely hard to get into using functors properly, but remember that you can only manipulate instances of the type parameterized by the functor through the interface/signature being exposed by the functor.