Can I match an expression that already raised an exception?


#1

Hi,

I’ve got this function

let isQuestion = iSay => {
  let lastPos = String.length(iSay) - 1;

  switch(String.rindex(iSay, '?'), lastPos) {
    | (q, l) when q === l => true
    | exception Not_found => false
    | _ => false
  }
}

but if I write it like this:

let isQuestion = iSay => {
  let questionPos = String.rindex(iSay, '?');
  let lastPos = String.length(iSay) - 1;

  switch(questionPos, lastPos) {
    | (q, l) when q === l => true
    | exception Not_found => false
    | _ => false
  }
}

it gives a run time error because let questionPos = String.rindex(iSay, '?'); raises an exception before we get catch it in the switch case. Is there anyway to “delay” this exception from being raised or some other way to handle it in the switch case?


#2

In general if you want to delay evaluating something you can use a function or a lazy value:

// Function
let questionPos = () => String.rindex(iSay, '?');
// Lazy
let questionPos = lazy String.rindex(iSay, '?');

Which one you’d use would depend on your requirements:

  • Function if you want to evaluate multiple times, or if you just want to keep things simple and you know you’ll never call the function more than once
  • Lazy value if you want to evaluate only once

To get the value out and handle the exception, you can call the function or force the lazy value with Lazy.force(questionPos).

I would ask though–why not just leave the switch as it is?


#3

Hey @yawaramin, thanks!

// Lazy
let questionPos = lazy String.rindex(iSay, '?');

Does let foo = lazy () => "bar" mean that () => "bar" only gets evaluated once foo is used, e.g. in switch(foo) { ... }? Or, do I need to use switch(Lazy.force(foo)) { ... } in that case?

I would ask though–why not just leave the switch as it is?

I am a firm believer of using properly named variables. You end up with more lines of code but it’s easier to read and maintain.


#4

let foo = lazy () => "bar"

That’s almost certainly not what you want in this scenario. Like I said, you will want either the function value or the lazy value, not both at the same time. That would be a double layer of delayed evaluation that you don’t need.

If you do want to use a lazy value instead of a function value, the behaviour of a lazy value is that it has a type Lazy.t('a) which you can force as many times as you want (e.g. in a loop) and it will only be evaluated the first time. After that its result will be cached and returned (or its exception will be thrown) no matter how many times you force it again. See https://caml.inria.fr/pub/docs/manual-ocaml/libref/Lazy.html for details.


#5

Thanks! Very interesting. I went for the function approach