Comparing a string character with another character somehow implicitly casts one of them to int?

I encountered a weird error while comparing a string character with another character. I wasn’t able to find an explanation of this on the internet, so I would appreciate if anyone could explain what’s happening here! :pray:

I’m using base. Let’s start with a simple utop session:

utop # open Base ;;

utop # let p = "abc" ;;
val p : string = "abc"

utop # p.[String.length p - 1] ;;
- : char = 'c'

So far so good. I’m able to get the last character of the string. But when trying to compare this character with another, I see a weird error (attaching a screenshot to highlight a relevant part)

In the text, for a11y:

utop # if p.[String.length p - 1] = 'c' then print_endline "WORKS!!" ;;
Error: This expression has type char but an expression was expected of type int

My question: what actually happens here and why OCaml suddently complains that one of the types is int?

  • I tried using == but base outputs a deprecation warning.
  • I’m able to use Caml.(==) p.[String.length p - 1] 'c' but this looks weird. Is there a better way?

OCaml version:

$ ocaml --version
The OCaml toplevel, version 5.0.0

For context, I’m trying to implement a function append_path that appends two file paths and strips extra path separators, so append_path "foo/" "/bar" would be foo/bar and not foo//bar.

let append_path (p1 : string) (p2 : string) =
  match () with
  | () when String.is_empty p1 -> p2
  | () when String.is_empty p2 -> p1
  | () when Caml.(==) p1.[String.length p1 - 1] '/' && Caml.(==) p2.[0] '/' ->
    p1 ^ String.drop_prefix p2 1
  | _ -> p1 ^ p2

If anyone has any suggestions on making it more idiomatic or reusing some of the existing functions, I would appreciate that a lot as well! :hugs:

open Base makes (=) work on int only by shadowing the polymorphic comparison operator.

You can replace it by Char.equal:

if Char.equal p.[String.length p - 1] 'c'

You can also locally open the Char module to make (=) work on char.

6 Likes

There will probably be many opinions on what is an idiomatic implementation of the function you talk about, but in any case I’ve never seen this construct being used before:

match () with
| () when cond₁ -> ...
| () when cond₂ -> ...
| _ -> ...

why not just:

if cond₁ then ... else
if cond₂ then ... else
  ...
2 Likes

You already got a narrowly targeted answer, here’s a suggestion.

Arguably this is more idiomatic (Oca)ml:

match p.[String.length p - 1] with
| 'c' -> print_endline "WORKS!!"
| _  -> ()
3 Likes

I just wasn’t sure how parsing in OCaml works exactly, and I like having guards with | aka in Haskell, so I came up with this syntax on the spot as an alternative to nested if-then-else expressions.