Какой отличный вопрос. Для этого предназначен тип 'опция ( документация , Q & A ).
Ваш find_value
может выглядеть следующим образом:
fun find_value [] _ = NONE
| find_value ((key,value)::pairs) key1 =
if key = key1
then SOME value
else find_value pairs key1
Теперь ваши два примера будут:
- find_value [("goo",~1.0),("boo",5.0)] "goo";
val it = SOME ~1 : real option
- find_value [] "goo";
val it = NONE : 'a option
Теперь абоненты find_value
должны проверить, существует ли значение, например, с:
case find_value "foo" of
NONE => ... do some error handling ...
| SOME value => ... proceed ...
Менее безопасная альтернатива - исключения:
exception NotFound of string
fun find_value [] key1 = raise NotFound key1
| find_value ((key,value)::pairs) key1 =
if key = key1
then value
else find_value pairs key1
Преимущество здесь в том, что вам не нужно распаковывать результат, в зависимости от того, найден он или нет, и если вы не знаете, как правильно обработать, что значение не найдено, вы можете просто отложить от перехвата исключения до нужной точки в стеке вызовов.
Но недостатком является то, что вы должны помнить, что нужно перехватить исключение, и что вы не можете быть уверены, что код, вызывающий другой код, будет свободен от исключений, поскольку любая функция, которая вызывает исключение, будет "портить" своих вызывающих.
Как правило, избегать исключений в коде, когда вы учитесь использовать строго типизированный язык программирования, потому что тогда компилятор и вы сможете лучше рассуждать о возможных сценариях при запуске программы.