Sorry, not getting it with mli

I feel quite dumb! :upside_down_face:

I don’t get it with making an .mli for a .ml as a module. I boiled it down to the dumbes … er … simples form of my ignorance.
Here is the .ml (in sub-dir lib)

let make_point x' y' : point =
  {x = x'; y = y'}

and the corresponding .mli, also in sub-dir lib

type point = {
  x : float;
  y : float
}

val make_point : float -> float -> point

The dune file in lib is:

(library
 (name point))

and the “main” in bin (why on earth is a source in “bin”?!)

open Point
open Printf

let pt = make_point 1. 2. in
  printf "%f\n" pt.x

and last but not least the dune file in bin:

(executable
 (public_name test)
 (name main)
 (libraries point))

dune complains:

File "bin/main.ml", line 4, characters 26-28:
4 | let pt = make_point 1. 2. in
                              ^^
Error: Syntax error
File "lib/point.ml", line 2, characters 23-28:
2 | let make_point x' y' : point =
                           ^^^^^
Error: Unbound type constructor point
Hint: Did you mean int?

No matter what my desperate tries are, I can’t get that to work.
Yes, I have read https://ocaml.org/docs/modules but it looks like I missed something important.

1 Like

The type point should be in both the .ml and the .mli. From what you shared it seems to be only in the .mli currently.

For the bin folder, it’s because you are writing a binary. It’s just a convention though, you can rename that folder if you wish.

For the syntax error, it’s because you are at toplevel. What you want is

open Point
open Printf

let () =
  let pt = make_point 1. 2. in
  printf "%f\n" pt.x
3 Likes

OK, the second error (toplevel) was stupid enough!

But why do I have to redeclare the record in the ml-file? Only for the compiler to have a chance to complain about the .ml when I change the float to int in just one place and then he realizes that they don’t match? Isn’t there a way to make this non-redundant and clean and only a single place to make changes at (subject: double maintenance)? Something in effect like ‘#include “point.mli”’

For the strange “bin” folder-naming: It is, what dune init project defaults to. For me, a bin-folder (or _build if you wish) contained (since now) what the compiler produced. And I rarely had a look at it. Sometimes, OCaml is more strange than necessary. And sometimes, I have the feeling the decissions were out of evil.

Yes, there is a special way to ‘include’ files. But most people will tell you (rightly) not to try to get too ‘clever’ with these kinds of tricks especially as a beginner. For the rest of your questions–highly recommend reading through the documentation, especially Your First OCaml Program · OCaml Documentation

Thank you for the link, and for sure, I did read that weeks ago. And re-read that in recent days.

The thing about the mli that is really disturbing is the sequence how this happens. The compiler first reads the mli and then complains in the ml that a declaration differs. He complains in the ml-flie, not the mli. So he knows in advance what the definition is. Why not use it? What makes this behaviour better than the simple and obvious way? Why is double-maintenance the way to go? Would you like to explain?

This question comes up reasonably often, so let me point you to the answer by the creator of the language: What is the reason of separation of module implementation and signatures in OCaml? - #33 by xavierleroy

2 Likes

Interesting read.
But I’m not complaining of the interface file. Au contraire, I’m a supporter of it. It is what the user sees and can use and has to know in the shortest possible form. That’s the place where I put my focus on when writing comments and hints about sequence, preconditions and warnings. The users doesn’t have to care about the implementation and need to understand how it was done. As a user, I don’t care how List.iter works, I just reley on it to work as described. And if there are special warnings like in Str.matched_string it is OK (like C’s pitfall strtok). I don’t want to find that out by wading through the implementation.
My point was why I have to double-maintain, escpecialy in the case of records, typedefs. A signature is understandable/defendable as it is a different representation.

By the way: I started programming with Pascal, then switched to Modula-2. But that was decades ago. Modula-2 was new at that time.

2 Likes

Partly because of the last point mentioned in the post I linked. An implementation can actually ‘fit’ different interfaces, even inside the same project, because you might want to expose different ‘views’ of the module to different consumers. Eg say you have a module:

type ratio = { p : int; q : int }

let ratio p q = { p; q }
let add ratio1 ratio2 = ...

At one level of your project, you might want to expose an interface like:

type ratio = { p : int; q : int }

val ratio : int -> int -> ratio
val add : ratio -> ratio -> ratio

At another level you might want to expose a more tightly constrained interface:

type ratio (* Implementation structure hidden *)

val ratio : int -> int -> ratio
val add : ratio -> ratio -> ratio

So there is not necessarily a single canonical interface for any given implementation. So we can’t automatically say that we should just copy over the type between the interface and the implementation.

3 Likes

For the strange “bin” folder-naming: It is, what dune init project defaults to. For me, a bin-folder (or _build if you wish) contained (since now) what the compiler produced.

You are right. Compared to Unix, the naming convention is different in many OCaml projects and in the tutorials. bin and lib folders do not contain binaries but sources. This needs to be clarified in the tutorials that use this convention. Thanks for pointing this out. I’ve just created an issue on that. Let us know if it captures the difficulty you experienced.

2 Likes

Isn’t there a way to make this non-redundant and clean and only a single place to make changes at (subject: double maintenance)? Something in effect like ‘#include “point.mli”’

For this, you can use the trick provided in this blog post. The idea is to use a .ml file to store your types, and included it both in the mli and the ml files.

2 Likes

Yes, that was the confusing (or stunning) point I have/had.
Why not keep the usual habbit and place sources in a “source” folder and maybe have any libs that are project-related in there. Placing “bin” and “lib” aside also is strange. The folder “test” should go into sources too.
Now, you can have whatever you want in “_build”, I consider this as “not my business”. Even if dune copies source files there. For whatever reason, I won’t touch them, because the naming signals that.
Maybe this will break dune, I don’t know. Or, show a set of dune files to follow the common structure. That would help both camps (of thinking or stubbornness if you want).

Oh, and while you are at it, please add the pair-of-quotes-oddity to the description of comments.
Unexpected comments behavior Thanks!

Thanks for your efforts.

1 Like

Have a second .mli with a different name? To increase readability? If someone, as a user, wants to ignore how type ratio looks like, he just can ignore it.
And with a more complex module, you end up with ‘modules_without_implementation’. Just to have to make a fix somewhere else.

As M.R. Clarkson says: “OCaml programming: correct + efficient + beautiful”
Or, as Stroustrup said at a conference at around 2000 “If I would have known where C++ is now, I wouldn’t have started it”.

That was just an example to show you that there is no definitive single interface for an implementation or vice-versa. In OCaml it’s very important that interfaces and implementations are independent of each other and are not in lockstep. You will see plenty of examples of this as you learn more, eg when you start creating maps and sets using the standard library.

1 Like

I agree that dune init creating a bin/ directory is confusing since bin/ is one of a few conventional output directories (the others are build, obj and target). And it is confusing with no benefit so you might want to put the feedback in Issues · ocaml/dune · GitHub. I don’t think the bin and obj “reservation” is well-known in OCaml circles since it is (mostly) a Microsoft standard.

There is a benefit to separating source code for executables from libraries. There is less typing needed to configure Dune.

I think bin is more of a Unix thing: /bin, /usr/share/bin, and so on.

However it would be helpful for the OP to remember that dune models the output project structure as a kind of ‘mirror’ of the source directory structure, so in that context you would be running eg dune exec ./bin/main.exe. Basically you set up your source directory structure in such a way that the compiled outputs end up in subdirectories like lib/ and bin/ where traditionally in Unix compiled artifacts would be placed.

1 Like

You are proving my point that bin is not well-known in OCaml circles as a conventional output folder, independent from /usr/bin/ and /bin: build - What are the obj and bin folders (created by Visual Studio) used for? - Stack Overflow

You could have exe/ and lib/ folder instead of bin/ and lib/ without confusing anybody. Not only that, it would mirror the Dune (executable) and (library) DSL language better. It seems minor, but this is the first interaction someone has with OCaml so tiny changes can make a big difference.

But my point is that it’s a convention from Unix, not OCaml, as your examples /bin and /usr/bin show. Unix application packages are often laid out with subdirectories lib/, bin/, etc/, that mirror the destinations their files would be installed into finally. So it can make sense if you think of it as trying to lay out the sources in a similar directory structure.

In any case I’m not trying to make a case for why this should remain the way it is, I’m just saying that maybe this isn’t a totally random and inexplicable choice. I am perfectly OK with renaming it to exe or app etc.

Granted, and I think we are saying the same thing then.

The tradeoffs drawbacks for bin mentioned are having to a) unlearn what that folder means for a percentage (25%?) of devs and b) learn what the Unix filesystem conventions.

And the benefits of the status quo are mimicking the Unix-based opam folder structure.

All before writing your first line of OCaml code (since this thread seems to be based off dune init based output).

I think that is good UX feedback, whether or not it is acted upon.

1 Like

Personally I always found it a bit confusing. If a directory has a bin subdirectory I usually expect to find scripts or binary executables in there.

1 Like

I’ve created PR#2568 that adds text on the role usually assigned to bin, lib and _build folders. I’d love to have your feedback. It touches tutorials Your First OCaml Program, Modules and Libraries With Dune.

1 Like