Making use of Warning 27: Unused variable

Is there a way to make the first use of a variable not qualify as “use” of a variable, for example by an annotation?
I find that the warning useful to clean up unused code, but often I run into the problem of the variable essentially being unused, but pretty-printed for debug purposes:

let foo bar baz =
  Logs.debug (fun m -> m "bar: %s  baz: %s" bar baz);
  baz (* bar is unused, but since the debug message "uses" it, I don't get the error *)

I guess I am looking for something like

let foo bar baz =
  (Logs.debug [@dont'register'use]) (fun m -> m "bar: %s  baz: %s" bar baz);
  baz (* bar is unused, but since the debug message "uses" it, I don't get the error *)

or

let foo bar baz =
  Logs.debug (fun m -> m "bar: %s  baz: %s" (bar [@dont'register'use]) baz);
  baz (* bar is unused, but since the debug message "uses" it, I don't get the error *)

You can use a leading underscore to suppress “unused” warnings. This doesn’t satisfy your “first use only” requirement, but it might be enough, or at least better. (And you might already know about this, and it isn’t sufficient.)

let foo _bar baz =
  Logs.debug (fun m -> m "bar: %s  baz: %s" _bar baz);
  baz

Here, if the Logs.debug line is removed, or conditionally compiled out, the warning won’t trigger.

You could also make sure warning 27 is only enabled when debug statements are not filtered out. I think this makes sense, since it is a good idea to have comprehensive warnings and debugging enabled during development, and to disable warnings during opam builds to avoid accidental breakage for new compiler versions and new library versions. (Strictly speaking I don’t think warning 27 needs be be disabled during production builds, since it I think it only depends on the application itself, but other warnings, like deprecations and warnings which depends on the analysis-capabilities of the compiler should be disabled for production builds.)

@atavener I’m trying to do the exact opposite of that; in this case I do want the warning to trigger regardless of the Logs.debug call. :slight_smile:

I’m not sure how to do conditional compilation like you suggest, would you use a preprocessor like cppo?

I suppose you could rebind the variables by having the logging function return them:

let debug print = if true then print ();;

let foo bar baz =
  let bar, baz = debug (fun () -> Printf.printf "%s %s\n" bar baz; bar, baz) in
  baz

which gives:

Characters 24-27:
Warning 27: unused variable bar.
val foo : 'a -> 'b -> 'b = 

No, this doesn’t work. The following works and looks a little bit more ridiculous:

let debug_mode = ref true

let debug print =
  if !debug_mode then print ()

let foo bar baz =
  let bar, baz =
    debug (fun () -> Printf.printf "%s %s\n" bar baz);
    bar, baz
  in
  baz

ok, this might be slightly better:

let debug_mode = ref true

let debug print =
  if !debug_mode then print ()

let foo bar baz =
  debug (fun () -> Printf.printf "%s %s\n" bar baz);
  let bar, baz = bar, baz in
  baz

@mjambon Thank you, that’s a nice hack!

@mjambon continuing your idea, I think I like this pattern:

let foo dbg_bar dbg_baz =
  let bar, baz = dbg_bar, dbg_baz in
  let dbg_catenated = bar ^ baz in let catenated = dbg_catenated in
  Logs.debug (fun m -> m "Debug 1: %s %s" dbg_bar dbg_baz);
  Logs.debug (fun m -> m "Debug 2: %s" dbg_catenated);
  baz

This has the benefit of not having to rebind the variables after each debug statement.

Ah, sorry! I somehow misinterpreted even though you wrote clearly.

As for conditional compilation, that was just an assumption which followed my misinterpretation – I thought you might be presenting the compiler different views, with and without debug lines.

I totally bodged my first post to Discuss. Good start. :stuck_out_tongue:

2 Likes

@atavener no worries, your idea was good; removing debug code by conditional compilation would still solve my problem! :slight_smile: Unfortunately I haven’t really found a nice way to do conditional compilation without hacky solutions that requires spending ages on the build system.

Coming from Java-land, this kind of check reminds me of an IDE inspection, as opposed to something that the compiler would do. Since this is OCaml-land, however, it makes sense to me that the compiler would provide this functionality, similar to how one can turn off warnings like:

let[@warning "-27"] foo a b =
  Printf.printf "%d" a;
  b + 1

Something like the following makes sense to me maybe? :

let[@variable_usage_counting "off"] debug_print s a =
  Printf.printf s a
2 Likes

@Caltelt Yes, that would be ideal, I was hoping that exact annotation already existed and that I had just missed it in the documentation. :slight_smile: