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.
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).
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.
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
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
´´´
To add another perspective, the dot character is used for two different things here:
Opening or specifying a module.
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:
The dot in List.hd is the module path dot. It means refer to the identifier hd in the module List.
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.
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:
If the thing to the left of the dot is a module, then it’s a module path dot.
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).
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