Compiler hacking help: where did the unification go?

I have this funcion
val Genprint.pr : 'a -> unit = “%typeof”
and when I use it like this:
(Genprint.pr 0)
_ : int = 0
I get the expected result because I have an application and access to the argument
type.
when I use it thusly:
let f = Genprint.pr in f 0
I have a non-application of pr but I was relying on unification to get 'a instantiated
in the type of the expression I intercept at translprim.ml/transl_primitive/Texp_ident.
so after the unification process when all is done that will be done.
my understanding of unification (not great) led me to expect that the term (f 0)
would unify f’s type to int -> unit and thus let f=Genprint.pr … would cause
the fresh copy of Genprint.pr’s 'a -> unit to be unified accordingly, so 'a becomes int.
but all I see is a Tvar None ! at the translation-to-lambda stage.
can anyone say why my expectation is unreasonable?

OCaml (like other MLs) implements let-polymorphism, thus the function f is fully polymorphic.

In general, it is a bad idea to rely on compiler primitive specialization like this: this kind of trick shatters once any polymorphic function is involved, like you have seen.

This is basically the same issue that required in F# the introduction of “statistically resolved types” that only works with always inlined function.

I see! thanks.
that leaves it to a macro or syntax extension then.
about breaking out type information: I understand this maybe
controversial.
but printing out a value at any point in a computation rather than waiting for it’s completion a’la toplevel is a special case:
a) it’s a rapid debug aid
b) the infrastructure is already there

regarding a), toggling @@deriving is great if the declaration isn’t buried under a mountain of dependencies. so a builtin way of seeing a problem value is a real boon.

printing aside, isn’t a portable(non-core), accessible type representation desirable? I once wrote a database app that would check the type assumptions of connecting applications about its contents based on an analogue of Types.type_expr.
there must be fundamental doubts about that sort of thing otherwise such would be in the compiler by now, no?

I don’t see how? Both of those have access to even less type information than a compiler primitive.

Except that it cannot work inside polymorphic function nor can it work with abstract types. In brief, it runs contrary to a lot of OCaml’s design decision. An extreme example of the issue might be a function defined as:

let option item ppf x = match x with
  | Some x -> Format.fprintf ppf "Some %a" item x
  | None -> Format.fprintf ppf "None"

let result item ppf x = match x with
  | Ok x -> Format.fprintf ppf "Ok %a" item x
  | Error x -> Format.fprintf ppf "Ok %a" item x

let rec random_walk: 'a 'never. 
(Format.formatter -> 'a -> unit) -> 'a -> 'never =
  fun printer x ->
  Format.printf "%a@," printer x;
  if Random.float 1. > 0.5 then
    random_walk (option printer) (Some x)
  else
    random_walk (result printer) (Ok x)

How do you expect to be able to print x by peeking at its type?

I don’t see how? Both of those have access to even less type information
than a compiler primitive.

it would enforce supplying an argument:
print s x =>> Genprint.pr (typeof x) s x

and if the user were to inadvertently put it in a context where x was polymorphic they could either get an error/warning to that effect or just wait and see <poly> and
be reminded of what the (hypothetical) docs warned against.

being able to drop something as uncomplicated as ’ print “thingy” x ’ into one’s code has some value.
even “…%s…\n” is too much trouble.

How do you expect to be able to print x by peeking at its type?

currently I have:
let outv = outval_of_value env x ty in
let pty = Printtyp.tree_of_type_scheme ty in
let it = Outcometree.Ophr_eval (outv, pty) in
!Toploop.print_out_phrase ppf it

I only mentioned macro/syntax extension in response to my
inability to conveniently alias the printing function thusly:

let pr = Genprint.pr

and forgetting module aliasing/opening!
so intercepting the “%typeof” primitive does the job of capturing the type and
an explicit argument of (typeof x) isn’t needed.

If you want to print or introspect values, you might be interested in my tagl branch.
It keeps enough meta-data to get a meaningful information about values (essentially how they look with OCaml syntax).

# Register my custom repository
opam repository add --set-default let-def https://github.com/let-def/my-opam-repo.git
# Create a new switch with the instrumented compiler
opam switch create 4.06.1+tagl
# Install utop, for the purpose of testing
opam install utop
# Install a library exposing the new introspection features
opam install ocaml-introspect

A sample session:

$ utop
# #require "ocaml-introspect";;
# Var_dump.var_dump [`A;`B;`C];;
[`A; `B; `C]
- : unit = ()
1 Like

I get an error adding the repository:

[ERROR] Could not update repository “let-def”: OpamDownload.Download_fail(_, “curl: code 404 while
downloading https://github.com/let-def/my-opam-repo/index.tar.gz”)
[ERROR] Initial repository fetch failed

I clicked around your repo and didn’t see any code to examine!?
so this introspection module lives outside the ocamltoplevel.cma sphere?

The URL was wrong.

Introspection affects the whole toolchain. It is not an OCaml module, it is an extension to the compilation scheme.

So what is the right url? I used the one you posted…

Can I examine the code? I’m sorry but I’m new to this stuff and it
isn’t obvious where things are after clicking everything in sight on
github.

OK found the introspection code. there’s hope for me yet.

I fixed the URL in my post:

opam repository add --set-default let-def https://github.com/let-def/my-opam-repo.git

I had forgotten the .git at the end.

The main thing is to store meta-data in block values (in the profinfo field, https://github.com/let-def/ocaml/blob/tagl/byterun/caml/mlvalues.h#L82).
For that, all allocation sites are augmented with the metadata to store (of type Taglib.t):
https://github.com/let-def/ocaml/blob/tagl/bytecomp/lambda.mli#L67
https://github.com/let-def/ocaml/blob/tagl/bytecomp/taglib.ml#L8

These metadata are produced during compilation, for instance https://github.com/let-def/ocaml/blob/tagl/bytecomp/translcore.ml#L835 .

The rest of the changes are just to propagate these metadata to runtime.

2 Likes