5
votes

This is the description of warning 27 from the OCaml manual:

27 Innocuous unused variable: unused variable that is not bound with let nor as, and doesn't start with an underscore (_) character.

This warning is turned on by jbuilder --dev, and I'm curious to know in which cases people find it useful. For me, it's an annoyance to get warnings when I write code like this:

$ utop -w +27
utop # fun (x, y) -> x;;
Characters 8-9:
Warning 27: unused variable y.
- : 'a * 'b -> 'a = <fun>

or like that:

utop # let error loc msg = failwith (loc ^ ": " ^ msg);;
val error : string -> string -> 'a = <fun>
utop # let rec eval = function
| `Plus (loc, a, b) -> eval a + eval b
| `Minus (loc, a, b) -> eval a - eval b
| `Star (loc, a, b) -> eval a * eval b
| `Slash (loc, a, b) ->
    let denom = eval b in
    if denom = 0 then
      error loc "division by zero"
    else
      eval a / denom
| `Int (loc, x) -> x
;;
Characters 33-36:
Warning 27: unused variable loc.
Characters 73-76:
Warning 27: unused variable loc.
Characters 112-115:
Warning 27: unused variable loc.
Characters 287-290:
Warning 27: unused variable loc.
val eval :
  ([< `Int of 'b * int
    | `Minus of 'c * 'a * 'a
    | `Plus of 'd * 'a * 'a
    | `Slash of 'e * 'a * 'a
    | `Star of 'f * 'a * 'a ]
   as 'a) ->
  int = <fun>

I know that prepending an underscore to the identifiers as in _loc suppresses the warnings, but it's not compatible with my notions that:

  1. variables starting with an underscore are ugly and are meant for use in generated code, hidden from the programmer;
  2. a name given to something should not have to change based on how it's used (including unused).

Using underscores, the code becomes:

(* Here we have _loc or loc depending on whether it's used. *)
let rec eval = function
| `Plus (_loc, a, b) -> eval a + eval b
| `Minus (_loc, a, b) -> eval a - eval b
| `Star (_loc, a, b) -> eval a * eval b
| `Slash (loc, a, b) ->
    let denom = eval b in
    if denom = 0 then
      error loc "division by zero"
    else
      eval a / denom
| `Int (_loc, x) -> x

or

(* Here it can be hard to know what _ stands for. *)
let rec eval = function
| `Plus (_, a, b) -> eval a + eval b
| `Minus (_, a, b) -> eval a - eval b
| `Star (_, a, b) -> eval a * eval b
| `Slash (loc, a, b) ->
    let denom = eval b in
    if denom = 0 then
      error loc "division by zero"
    else
      eval a / denom
| `Int (_, x) -> x
2
I personally find it useful that unused bindings are differentiated from used ones. Specifically, when I read Plus (_loc, a, b), I know that I don't need to pay attention to _loc. Depending on the scenario, Plus (_, a, b) can be even better; it clearly shows we only care about positional values a and b here; it doesn't matter what _ is, it's discarded/unused in the body anywayMulan
A warning is exactly that: a message that something may be wrong. If you define a variable somewhere and don't use it, it may indicate a typo or something, as in let add a b = a + a.Richard-Degenne
@user633183 FWIW the way I see this is (1) an unused variable doesn't cost an extra computation and (2) it just gives a name to yet another item in the environment and it tells me it's available if I want to use it. I read _loc as something auto-generated that I should probably not touch and I suppose not everyone reads it this way.Martin Jambon
@Richard-Degenne of course we don't want warnings when everything is fine. Unlike @ivg I can't remember having a bug due to not using a variable introduced in a pattern (other than let or as). However I do remember reporting the wrong location in camlp4 code due to having code like let f loc x = match x with Foo (_, y) -> error loc "..." which should have been let f loc x = match x with Foo (loc, y) -> error loc "...". ymmvMartin Jambon
@MartinJambon: Which is the point of the underscore notation. To the developer or to the compiler, it tells "Don't worry about it, I know what I'm doing."Richard-Degenne

2 Answers

10
votes

It is very useful in the monadic code, where instead of the common syntactic let bindings you're forced to use monadic >>= bind operator. Basically, where

let x = something in
code

translates to

something >>= fun x ->
code

If x is not used in code then only with the 27 warning enabled the latter will be highlighted, while the former will produce a warning by default. Enabling this warning, revealed lots of bugs for us. For example, it showed us that this code is buggy :)

Another source of use cases are higher-order functions, i.e., map, fold, etc. It captures one of the most common bugs:

let bug init = 
   List.fold ~init ~f:(fun acc xs -> 
     List.fold ~init ~f:(fun acc x -> x :: acc))

Concerning the ugliness, I totally agree that underscores are ugly, but in most cases, this is the main purpose of them - to highlight the suspicious code. Concerning the example, that you're showing, in the modern OCaml it could be easily addressed with the inline records, e.g.,

type exp = 
  | Plus of {loc : loc; lhs : exp; rhs: exp}
  | ...

so that instead of using the underscores, you can just omit the unused field,

 let rec eval = function
   | Plus {lhs; rhs} -> eval lhs + eval rhs

You can use the same approach without using inline records by sparing some extra space in your program and defining all those records separately. The real-world example.

2
votes

For me this warning is useful in order to remind me to explicit more my intention. If we take your example :

fun (x, y) -> x;;

Your intention is to use only the first element. If we rewrite it this way :

fun (x, _ ) -> x;;

You use a pattern matching in the parameter to make your code more concise, but you explain your intention of using only the first element. The added value in this example is small, related to the very simple implementation. But in real life functions, this warning promote a good habit in coding.