Strange form for function calling

While learning more about lwt I came across this function calling syntax. I’ve tried everything to find more about it, with no luck so far. The form I’m talking about basically is.

Modulex.(Whathe x).methodfoo "omg"

like seen in an Lwt example from https://github.com/ocsigen/lwt

let google = Lwt_unix.((List.hd addresses).ai_addr) in

This irritates me to no end! Because this would mean we have some compile time evaluation going on. And cannot be.

Please help :-).

Thanks
uno

1 Like

The function call syntax is the same. What you’re stumbling on is probably the local open syntax.

So, let google = Lwt_unix.((List.hd addresses).ai_addr) in is equivalent to let google = let open Lwt_unix in (List.hd addresses).ai_addr in which itself is equivalent to let google = (List.hd addresses).Lwt_unix.ai_addr in (assuming addresses comes from somewhere other than Lwt_unix).

1 Like

whoa that was fast. Thanks a lot. I’m glad to be able to put name on it. Never heard of ‘local open’ althouth I was sure to read every book abou Ocaml. I must have missed something.

Ok, now that I’m actually try to write this in a more “traditional” manner (without local open), I’m still confused

You say

 let google = let open Lwt_unix in (List.hd addresses).ai_addr

is equivalent to

 let google = (List.hd addresses).Lwt_unix.ai_addr in

But how can I write this without this (func “foo”).meth syntax?

Sorry, I still don’t get it

Not sure to understand the question.

Is the following way to write it more understandable to you?

let google =
  let first_address = List.hd addresses in
  first_address.Lwt_unix.ai_addr in
  ...

Salutations

1 Like

List.hd addresses returns a record of type Lwt_unix.addr_info. That record has a field Lwt_unix.ai_addr. You could first name the result of List.hd addresses:

let google = let open Lwt_unix in let address = List.hd addresses in address.ai_addr in

Thanks alot this goes in the right direction.

But I’m sorry to say I’m still confused about this part:
first_address.Lwt_unix.ai_addr

For me it looks like Lwt_unix.ai_addr is called upon an expression that is evaluated on runtime.

Sorry for asking this stupid questions.

Suppose you want to access field ´f´ of record ´r´.

If the type of ´r´ is known (ie if the module ´M´ where ´r´ type was
defined is opened) you can write ´r.f´. But if ´M´ is not opened, you have
to help the compiler by prepending the module in front of the field name:
´r.M.f´.

And this can become more complexe if f type is a record defined in module
M2. Say that you now want to access the f2 field of ´r.M.f´:

´´´ocaml
let rf = r.M.f in
let rff2 = rf.M2.f2
´´´

or directly:
´´´ocaml
let rff2 = r.M.f.M2.f2
´´´

Salutations

2 Likes

To add another perspective, the dot character is used for two different things here:

  1. Opening or specifying a module.
  2. Accessing a field of a record.

So, taking

Lwt_unix.((List.hd addresses).ai_addr)

This means

With module Lwt_unix open (its identifiers visible), “interpret” (List.hd addresses).ai_addr.

However, the only identifier from Lwt_unix in that expression is the record field name ai_addr. So, the expression could have been

(List.hd addresses).Lwt_unix.ai_addr

This means

Execute List.hd addresses. The result will be a record of some type (that we later see is from Lwt_unix) that has an ai_addr field. Then, get the value of that ai_addr field. The ai_addr field is defined in Lwt_unix.

I don’t believe the following is valid OCaml record syntax, but we could make this expression a bit clearer by inserting parentheses:

(List.hd addresses).(Lwt_unix.ai_addr)

The dots have these meanings:

  1. The dot in List.hd is the module path dot. It means refer to the identifier hd in the module List.
  2. The dot in Lwt_unix.ai_addr is also the module path dot. It means refer to the identifier ai_addr in the module Lwt_unix.
  3. The middle dot is the record access dot. It means “look up” field Lwt_unix.ai_addr in the record produced by the expression on the left of the dot.

The way you know which dot is of which kind is:

  1. If the thing to the left of the dot is a module, then it’s a module path dot.
  2. If the thing to the left is an expression, then it’s a record field access dot.

The module dot is easy to spot because the name to the left will be capitalized. Everything else is the record access dot.

EDIT: Actually, the dot is also used for string and array access, with syntax e.[e] and e.(e). You detect this when you see the bracket or parenthesis (this is why one of the lines above wasn’t valid record access syntax).

2 Likes

And to write this without any “magic” at all, you would do

let first_address = List.hd addresses in
let google = first_address.Lwt_unix.ai_addr in
(* ... *)

Perhaps it should be that way in the README :slight_smile:

EDIT: Argh, forgot @ttamttam already showed it, thanks @ttamttam :slight_smile:

To illustrate @ttamttam explanation with example in the REPL :

module M = struct
  type t = {f : int}
  let v = { f = 1 }
end

module M2 = struct
  type t = { g : M.t }
  let v = { g = M.v }
end;;
module M : sig type t = { f : int; } val v : t end
module M2 : sig type t = { g : M.t; } val v : t end

M2.v;;
- : M2.t = {M2.g = {M.f = 1}}

M2.v.M2.g;;
- : M.t = {M.f = 1}

M2.v.M2.g.M.f;;
- : int = 1

(* or like in the lwt example *)
M2.(v.g.M.f);;
- : int = 1
2 Likes

Thanks alot for your answers. A special thanks to antron for explaining the mixed example record field/Module lookup.

1 Like