Printing random data

In most languages I’ve worked with before there was a default printer for records and similarly complex types.

Does such thing exist in Ocaml? An example to illustrate

type matcher = {account: string;
                matches: string list}

print {account = "A"; matches = ["foo"; "bar"]}

This Stackoverflow answer seems to indicate there is no such thing but all answers are from 2011 so maybe something has changed in the meantime or there is a. new ecosystem solution for this.

For context: I’m mostly looking for this because I’m often finding myself doing print-debugging.

You might be interested in the fmt library, which gives lots of convenient functions for building your own pretty-printers. There is also the show plugin for ppx_deriving, which automatically derives pretty-printers for types with the deriving show annotation.

3 Likes

Let me second the suggestion you try [@@deriving show], it works really well. I’ve used it for my work and it’s quite easy to use. All you have to do is plop the magic annotation in after your type declaration, tweak your build to execute the correct ppx, and you’ll automatically get a show function that does what you want.

Hey, thanks for these recommendations.

I was able to install ppx_deriving and adjusted my jbuild file to load it:

(executable
 ((name hello_world)
  (preprocess (pps (ppx_deriving.std)))
  (libraries (csv
              base
              stdio))))

Then I got the following error though:

         ppx hello_world.pp.ml
    ocamldep hello_world.depends.ocamldep-output
      ocamlc hello_world.{cmi,cmo,cmt} (exit 2)
(cd _build/default && /Users/martin/.opam/4.04.0/bin/ocamlc.opt -w -40 -g -bin-annot -I /Users/martin/.opam/4.04.0/lib/base -I /Users/martin/.opam/4.04.0/lib/base/caml -I /Users/martin/.opam/4.04.0/lib/base/shadow_stdlib -I /Users/martin/.opam/4.04.0/lib/bytes -I /Users/martin/.opam/4.04.0/lib/csv -I /Users/martin/.opam/4.04.0/lib/ppx_deriving -I /Users/martin/.opam/4.04.0/lib/result -I /Users/martin/.opam/4.04.0/lib/sexplib/0 -I /Users/martin/.opam/4.04.0/lib/stdio -no-alias-deps -I . -o hello_world.cmo -c -impl hello_world.pp.ml)
File "hello_world.ml", line 31, characters 0-146:
Warning 3: deprecated: module Base.Format
[2016-09] this element comes from the stdlib distributed with OCaml.
Refering to the stdlib directly is discouraged by Base. You should either
use the equivalent functionality offered by Base, or if you really want to
refer to the stdlib, use Caml.Format instead
File "hello_world.ml", line 31, characters 0-146:
Error: Unbound value pp_account
  • The warning seems to be related to the fact that I’m using Base. Not sure if fixable but doesn’t appear fatal.
  • The error mentions pp_account which I googled at first until I realized that’s a type inside a type that I annotated with [@@deriving show]
  • After fixing that the compiler seems happy but putting show_account somewhere does not seem to have any effect.

Two questions:

  1. What am I missing?
  2. And can the warning be fixed?

Thanks a lot! :slight_smile:

Here’s an example using Base and Stdio. First, the jbuild file:

;; -*- scheme -*-

(jbuild_version 1)

(executables
 ((names (w))
  (libraries (base stdio))
  (flags (:standard -short-paths))
  (preprocess (pps (ppx_jane ppx_driver.runner)))
  ))

And here’s the code example:

open Base
open Stdio

type t = { a: int; b: float * float }
[@@deriving sexp]

let () =
  let x = { a = 3; b = (4.5,5.6) } in
  [%sexp (x : t)] |> Sexp.to_string_hum |> print_endline

And when you run it you get this output:

((a 3) (b (4.5 5.6)))

S-expression converters are present throughout Base and all the related libraries (Stdio, Core_kernel, Core, Async, Incremental, etc.), and so you can pretty much count on being able to serialize any data structure you encounter there, as well as anything you define on your own, via the deriving work.

I don’t know as much about ppx_deriving and why it’s not working here. In this example I’m using ppx_driver, but I’m pretty sure that ppx_deriving can be made to work with jbuilder. I think there are still some interoperability issues between the two that are being ironed out.

y

3 Likes

Hey Yaron,

very very nice of you to provide a minimal example :raised_hands:

I also just discovered that a simple example is present in the JBuilder documentation.

It took me a while to get it to work (for reasons I can no longer trace back) but I got it to work now. :slight_smile:

Also seeing s-expressions makes me happy, haha.
A few follow up notes/questions:


S-Expression pretty printing

((date ((day 08) (month 01) (year 2016))) (title x)

While most of the sexp seem to be pretty printed in some way I was surprised to see title and date on one line. Is the sexp printer purely breaking at a fixed line length? Proper pretty printing would be very nice :slight_smile:


Double semis (yeah :smirk:)

I really haven’t fully grokked when I have to use ;;. In the code below removing any of the double semis causes an error. From the various things I read my understanding is that you really only need ;;. in a REPL. And that you use ; when you have multiple (imperative) statements in the same function.


let () =

(* lots of stuff emitted for brevity - no double semis *) 

let c = Csv.of_channel ~separator:';' (Stdio.In_channel.create "txs.csv")
let rows = Csv.input_all c

let x row =
  let l = row |> to_dkb |> ledgerx_of_dkb in
  [%sexp (l : ledgerx)]
  |> Sexp.to_string_hum
  |> print_endline;; (* removing these ;; causes a syntax error *)

let first_row = (List.nth_exn rows 0) in
let l = first_row |> to_dkb |> ledgerx_of_dkb in
  [%sexp (l : ledgerx)] |> Sexp.to_string_hum |> print_endline;; (* same here *)

List.map ~f:x rows

Thank you :slight_smile:

I never use ;; in a .ml file, but only in the REPL to evaluate an expression. For instance, in the REPl I’ll write :

print_endline "hello world!";;
hello world!
- : unit = ()

As you can see, this expression evaluate to a value of type unit = (). So in a file, I’ll write :

let () = print_endline "hello world!"

because every expression have to be bound to a name (here () works only for expression of type unit), but I could also have write :

let _ = print_endline "hello world!"
let a_name = print_endline "hello world!"

For the semi ;, you can see it as an infix operator of type unit -> 'a. So the left part must be of type unit, the right part of any type and the whole expression has the same type as the right part. Example :

let i : int = (); 1

Thanks @kantian. Reading your explanation I’m thinking that my error is caused by my let () = expression not returning unit — does that sounds right?

I changed my code to the following now:

  row |> to_dkb |> ledgerx_of_dkb |> print_ledgerx_sexp

let print_first_row () =
  let l = (List.nth_exn rows 0) |> to_dkb |> ledgerx_of_dkb in
  [%sexp (l : ledgerx)] |> Sexp.to_string_hum |> print_endline

let main () =
  (* List.map ~f:x rows; *)
  print_first_row();
  print_endline "DONE";

main() (* unknown function since it's not regarded to be inside the toplevel let () = *)

the main() invocation isn’t regarded as being inside let () = however, thus it’s out of scope. How do you implement this kind of “main pattern”?

You can give this a shot, though it’s a bit of a pain to set up.

In the next Core release (coming very soon!), the Sexp_pretty pretty printer is available as print_s, so you can just write:

print_s [%sexp (foo : int list)]

to print a nicely formatted sexp.

y

Not sure I really understand what you try to achieve.

First, your code :

let main () =
  (* List.map ~f:x rows; *)
  print_first_row();
  print_endline "DONE";

main() (* unknown function since it's not regarded to be inside the toplevel let () = *)

is parsed in the same way this one :

let main () =
  (* List.map ~f:x rows; *)
  print_first_row();
  print_endline "DONE";
  main ()

the last line is part of the definition of main since you put a trailing ; after the print_endline. If you put a let rec to define main, the code will compile with a main of type unit -> 'a that will indefinitely print. :wink:

OCaml runtime evaluates expressions and binds them to names, so in toplevel you bind expressions to names. If you want a main pattern in your code, you have to do something like this :

let my_cool_stuff1 = (* your expression that defines it *)
let my_cool_stuff2 = (* idem *)

let main () = (* an expression that evaluates to () *)

(* now you call main like this *)
let () = main ()

values of type unit are evaluated only for their side effects. Example :

let main () =
  print_string "Give me an int and press enter : ";
  let i = read_int () in
  Printf.printf "you give me the value : %d\n" i

let () = main ()

If I understand what you want, you just have to change the end of your code with :

let main () =
  (* List.map ~f:x rows; *)
  print_first_row();
  print_endline "DONE" (* I removed the trailing ; *)

let () = main ()

Remark : List.map ~f yields another list, not a unit value (). If you want to iter a function for its side effects on a list, use List.iter ~f instead.

1 Like

To complete my previous answer with a more detailed example.

let safe_read cond msg err_msg =
  print_string msg;
  let rec loop () = match read_int () with
    | n when cond n -> n
    | n -> print_string err_msg; loop()
    | exception _ -> print_string "I need an int! so: "; loop ()
  in loop ()

let ask_list_values () =
  let cond n = true in
  let msg n = "Give me an int " ^
    if n = 1 then "(last one): "
    else Printf.sprintf "(%d remaining): " n
  in
  let rec loop l = function
    | 0 -> l
    | n -> loop (safe_read cond (msg n) "" :: l) (pred n)
  in
  safe_read
    (fun n -> n > 0)
    "How many values do you want to give? "
    "I need a positive number! so, how many? "
  |> loop []

let print_list l =
  print_char '['; 
  List.iteri
    (fun i v -> 
      (if i > 0 then print_char ' ');
      Printf.printf "%d;" v)
    l;
  print_char ']'

let main () =
  let l = ask_list_values () in
  print_string "You gave me this list of values: ";
  print_list (List.rev l);
  print_newline ()

let () = main ()

You can copy-past this in the REPL and add double semis ;;, here an example of interaction :

How many values do you want to give? -5
I need a positive number! so, how many? 5
Give me an int (5 remaining): 3                                                 
Give me an int (4 remaining): seven
I need an int! so: 7
Give me an int (3 remaining): 21
Give me an int (2 remaining): 2
Give me an int (last one): 42
You gave me this list of values: [3; 7; 21; 2; 42;]
val safe_read : (int -> bool) -> string -> string -> int = <fun>
val ask_list_values : unit -> int list = <fun>
val print_list : int list -> unit = <fun>
val main : unit -> unit = <fun>

You can see the different values that will be exported (and their type) if you put this code in a .ml file.

Thanks a lot, I think I understand now. I was using let () = at the very beginning of the file but if I understood you correctly I would just let all my functions at the top-level and then call them at the end when I define let () = .

Thanks also for the full example, I’ll take a closer look at that later :slight_smile:

You can have as many let () = as you like in your code. It is an expression which needs nothing, yields a unit and is evaluated at the point it is declared. It’s a typical scenario to see such expressions at the end of a module.

1 Like

Yeah, that’s my understanding now as well. Thanks :slight_smile:

It’s a typical scenario to see such expressions at the end of a module.

These are things I like to read as a beginner :slight_smile: (i.e. answering the “What’s normal?” question)

That’s a possibility, and the most used one. It depends on which values you want to export from your module. By default, each expression that is bound to an identifier at the toplevel is exported (you can restrict this using a .mli file) and can be used by any subsequent expression in the module.

In my full example, the values safe_read, ask_list_values, print_list and main are exported. I could also have put the let () = at the beginning of the file, but doing so I have to use let ... in binding form:

let () =
  let safe_read =
    (* its body *)
  in (* note the `in` I added *)
  let ask_list_values =
    (* its body *)
  in (* another `in` added *)
  let print_list =
    (* its body *)
  in (* another `in` added *)
  (* I inline main's body *)
  let l = ask_list_values () in
  print_string "You gave me this list of values: ";
  print_list (List.rev l);
  print_newline ()

and this module will export no values. If you copy-past this in the REPL and add a double semis ;; to launch evaluation, you could not call any function anymore since they are not defined at the toplevel but only locally (i only used let ... in binding form).

1 Like