2013-08-27 2 views
11

Я учил себя F # в последнее время, и я родом из императивного (C++/C#) фона. В качестве упражнения я работал над функциями, которые могут делать вещи с помощью матриц, например, добавлять, умножать, получать детерминанты и т. Д. В этом отношении все идет хорошо, но я считаю, что, возможно, я не принимаю наилучших решений, когда это касается обработки недействительные входы, например:F #: Some, None или Exception?

// I want to multiply two matrices 
let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    // Here is where I am running to conceptual trouble: 
    // In a C# world, I would throw an exception. 
    if !sizeOK then 
    raise (InvalidOperationException("bad dimensions!") 
    else 
    doWork m1 m2 

Так что, хотя это технически работает, подходит ли это для функционального языка? Это в духе функционального программирования? Или это больше смысла переписывать его как:

let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if !sizeOK then 
    None 
    else 
    Some doWork m1 m2 

В этом случае я возвращаю вариант, который добавляет дополнительный слой вокруг матрицы, но я мог бы также использовать результаты функции, даже в отказе случаев (Нет) с сопоставлением с образцом и т. д. в какой-то более поздний момент в программе. Итак, есть ли наилучшая практика для этих типов сценариев? Что бы сделал функциональный программист?

+0

Я считаю, что последнее больше похоже на «дух» FP. Возможно, вы захотите взглянуть на монаду Maybe, а также на общий подход к работе с плохими входами. –

+0

Как сказано в ответе, я также часто реализую две функции, которые бросают ошибки и идентичную функцию try, которая возвращает опцию/выбор вместо метания. то я использую тот, который делает мой последний код более привлекательным :) для упрощения обработки параметров/выбора вы можете прочитать «железнодорожное ориентированное программирование»: http://fsharpforfunandprofit.com/posts/recipe-part2/ – stmax

ответ

8

Я склонен избегать исключений по следующим причинам:

  • .NET exceptions are slow
  • Исключения изменить управляющие потоки программ неожиданным образом, что делает его гораздо труднее рассуждать о
  • Исключения часто возникают когда вы можете отказоустойчиво, используя параметры.

В вашем случае, я буду следовать F # основная библиотека конвенций (например, List.tryFind и List.find и т.д.) и создать обе версии:

let tryMult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if not sizeOK then 
    None 
    else 
    Some <| doWork m1 m2 

let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if not sizeOK then 
    raise <| InvalidOperationException("bad dimensions!") 
    else 
    doWork m1 m2 

Этот пример не исключительный достаточно использовать исключения , Функция mult включена для совместимости с C#. Кто-то, использующий вашу библиотеку в C#, не имеет шаблонов, чтобы легко разлагать параметры.

Одним из недостатков вариантов является то, что они не дают причины, по которой функция не выдала значение. Здесь слишком много; как правило, Choice (или в любом монада Haskell термина) является более подходящим для error handling:

let tryMult m1 m2 = 
    // Assume that you need to validate input 
    if not (validateInput m1) || not (validateInput m2) then 
    Choice2Of2 <| ArgumentException("bad argument!") 
    elif not <| validateDims m1 m2 then 
    Choice2Of2 <| InvalidOperationException("bad dimensions!") 
    else 
    Choice1Of2 <| doWork m1 m2 

Жаль, что F # Основные недостатки функций высокого порядка для управления выбором. Эти функции можно найти в библиотеке FSharpX или ExtCore.

3

Я склонен идти со следующими рекомендациями:

Используйте исключение в функции, которая должна всегда иметь возвращаемые значения, когда что-то идет не так неожиданно. Это может быть, например, если аргументы не подчиняются контракту для функции. Это имеет то преимущество, что клиентский код упрощается.

Используйте опцию, когда функция иногда имеет возвращаемое значение для действительного ввода. Это может быть, например, получить на карте, где действительный ключ может не существовать. Тем самым вы вынуждаете пользователя проверять, имеет ли функция возвращаемое значение. Это может уменьшить количество ошибок, но всегда загромождает клиентский код.

Ваш случай несколько в промежутке. Если вы ожидаете, что в первую очередь будут использоваться в местах, где размеры действительны, я бы выбрал исключение. Если вы ожидаете, что код клиента часто вызывает его с недопустимым измерением, я бы вернул опцию. Я, вероятно, пойти с прежним, как это чище (смотри ниже), но я не знаю свой контекст:

// With exception 
let mult3 a b c = 
    mult (mult a b) c; 

// With option 
let mult3 a b c= 
    let option = mult a b 
    match option with 
    | Some(x) -> mult x b 
    | None -> None 

Отказ от ответственности: У меня нет профессионального опыта с функциональным программированием, но я TA в программировании F # на уровне выпускников.

2

Мне нравятся приведенные выше ответы, но я хотел добавить еще один вариант. Это действительно зависит от неожиданного результата и имеет ли смысл действовать. Если это редкое событие, и вызывающий абонент, скорее всего, не собирался терпеть неудачу, то исключение является вполне респектабельным. Код, чтобы поймать исключение, может быть много уровней выше, и вызывающий абонент, вероятно, не планировал сбой. Если это действительно обычный результат для операции сбой, Some/None одобрен, хотя он дает вам только два варианта и не дает возможности передать результат. Другой вариант - сделать дискриминированный союз возможностей. Это приводит к тому, что вызывающий объект может соответствовать различным результатам, является расширяемым и не заставляет вас делать каждый результат одним и тем же типом данных.

например.

type MultOutcome = 
    | RESULT of Matrix 
    | DIMERROR 
    | FOOERROR of string 


let mult a b = 
    if dimensionsWrong then 
     DIMERROR 
    elif somethingElseIDoNotLike then 
     FOOERROR("specific message") 
    else 
     DIMRESULT(a*b) 


match mult x y with 
    | DIMERROR -> printfn "I guess I screwed up my matricies" 
    | FOOERROR(s) -> printfn "Operation failed with message %s" s 
    | DIMRESULT(r) -> 
     // Proceed with result r 
Смежные вопросы