Что является функциональной заменой операторов if-then? - PullRequest
41 голосов
/ 15 февраля 2012

Я изучаю F # и функциональное программирование и пытаюсь делать вещи функциональным способом. Однако, когда дело доходит до переписывания некоторого кода, который я уже написал на C #, я застреваю в простых операторах if-then (те, которые только что-то делают, но не возвращают значение). Я знаю, что вы можете осуществить это в F #:

if expr then do ()

Тем не менее, я думал, что это был императивный подход к кодированию? Возможно, я недостаточно изучил функциональное программирование, но оно мне не кажется функциональным. Я думал, что функциональный подход заключается в том, чтобы составлять функции и выражения, а не просто выполнять операторы один за другим, что, как кажется, поощряет «если-то».

Итак, я что-то упускаю и если-тогда прекрасно в функциональном мире? Если нет, то каков функциональный эквивалент такого утверждения? Как я могу взять if-then и включить его в работу?

Редактировать: Возможно, я задал неправильный вопрос (извините, все еще довольно плохо знаком с функциональным программированием): Давайте рассмотрим пример из реальной жизни, который заставил меня даже спросить:

if not <| System.String.IsNullOrWhiteSpace(data) then do
    let byteData = System.Text.Encoding.Unicode.GetBytes(data)
    req.ContentLength <- int64 byteData.Length
    let postStream : System.IO.Stream = req.GetRequestStream()
    postStream.Write(byteData, 0, byteData.Length)
    postStream.Flush()
    postStream.Dispose()

Тело этого if-then ничего не возвращает, но я не знаю, как я мог бы сделать это более функциональным (если это вообще возможно). Я не знаю правильную технику для минимизации императивного кода. Учитывая природу F #, достаточно просто напрямую перенести мой C #, но у меня возникают трудности с его включением. Каждый раз, когда я достигаю такого оператора if в C # и пытаюсь перенести его в F #, я разочаровываюсь, что не могу придумать, как сделать код более функциональным.

Ответы [ 7 ]

35 голосов
/ 15 февраля 2012

Важным моментом, который до сих пор не упоминался, является разница между if .. then .. else и if .. then без ветви else.

If в функциональных языках

Функциональная интерпретация if заключается в том, что это выражение, которое оценивается до некоторого значенияЧтобы оценить значение if c then e1 else e2, вы оцениваете условие c, а затем оцениваете либо e1, либо e2, в зависимости от условия.Это дает вам результат if .. then .. else.

Если у вас просто if c then e, то вы не знаете, каким должен быть результат оценки, если c равен false, потому чтонет else ветки!Следующее явно не имеет смысла:

let num = if input > 0 then 10

В F # выражения с побочными эффектами, такие как printf "hi", возвращают специальное значение типа unit.Тип имеет только одно значение (записывается как ()), поэтому вы можете написать if, который дает эффект только в одном случае:

let u = if input > 0 then printf "hi" else ()

Это всегда оценивается как unit, нов ветке true он также выполняет побочный эффект.В ветке false он просто возвращает значение unit.В F # вам не нужно писать бит else () вручную, но концептуально он все еще там.Вы можете написать:

let u = if input > 0 then printfn "hi"

По поводу вашего дополнительного примера

Код выглядит для меня отлично.Когда вам приходится иметь дело с API, который является императивным (например, с множеством библиотек .NET), тогда лучшим вариантом будет использование таких императивных функций, как if с unit -обратной ветвью.

Youможет использовать различные настройки, например, представлять ваши данные, используя option<string> (вместо просто string с null или пустой строкой).Таким образом, вы можете использовать None для представления отсутствующих данных, а все остальное будет правильным вводом.Затем вы можете использовать некоторые функции более высокого порядка для работы с опциями, например, Option.iter, который вызывает данную функцию, если есть значение:

maybeData |> Option.iter (fun data ->
    let byteData = System.Text.Encoding.Unicode.GetBytes(data)  
    req.ContentLength <- int64 byteData.Length  
    use postStream = req.GetRequestStream()  
    postStream.Write(byteData, 0, byteData.Length) )

Это на самом деле не менее важно, но этоболее декларативный, потому что вам не нужно писать if самостоятельно.Кстати, я также рекомендую использовать use, если вы хотите Dispose объект автоматически.

13 голосов
/ 15 февраля 2012

В функциональном мире нет ничего плохого.

Ваш пример на самом деле похож на let _ = expr, поскольку expr имеет побочные эффекты, и мы игнорируем его возвращаемое значение.Более интересный пример:

if cond then expr

, что эквивалентно:

match cond with
| true -> expr
| false -> ()

, если мы используем сопоставление с образцом.

Когда условие простое или имеется только одно условное выражение, if-then более читаемо, чем сопоставление с образцом.Более того, стоит отметить, что все в функциональном программировании является выражением.Так что if cond then expr на самом деле является сокращением if cond then expr else ().

Если-то само по себе не является обязательным условием, использование «--тогда» в качестве утверждения является обязательным способом мышления.По моему опыту, функциональное программирование больше относится к образу мышления, чем к конкретным потокам управления в языках программирования.

РЕДАКТИРОВАТЬ:

Ваш код полностью читаемый.Некоторые незначительные моменты избавляются от избыточного ключевого слова do, аннотации типа и postStream.Dispose() (с помощью ключевого слова use):

if not <| System.String.IsNullOrWhiteSpace(data) then
    let byteData = System.Text.Encoding.Unicode.GetBytes(data)
    req.ContentLength <- int64 byteData.Length
    use postStream = req.GetRequestStream()
    postStream.Write(byteData, 0, byteData.Length)
    postStream.Flush()
8 голосов
/ 15 февраля 2012

Обязательным является не выражение if, а то, что входит в выражение if. Например, let abs num = if num < 0 then -num else num - полностью функциональный способ написания функции abs. Он принимает аргумент и возвращает преобразование этого аргумента без побочных эффектов. Но когда у вас есть «код, который только что-то делает, а не возвращает значение», , тогда вы пишете что-то, что не является чисто функциональным. Цель функционального программирования - минимизировать ту часть вашей программы, которая может быть описана таким образом. То, как вы пишете свои условия, является тангенциальным.

7 голосов
/ 15 февраля 2012

Для того, чтобы написать сложный код, вам нужно перейти в какой-то момент. Существует очень ограниченное количество способов сделать это, и все они требуют логического прохождения раздела кода. Если вы хотите избежать использования if / then / else, то можно использовать чит / loop / while / repeat, но это сделает ваш код менее разумным для поддержки и чтения.

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

Например, если у нас есть функция foo(int, bool), которая возвращает что-то другое в зависимости от того, является ли bool истинным или ложным, почти наверняка будет оператор if где-то в foo(). Это совершенно законно. НЕДОПУСТИМО, чтобы была функция foo(int), которая возвращает что-то другое в зависимости от того, вызывается ли она в программе впервые. Это функция с сохранением состояния, которая усложняет жизнь любому, кто поддерживает программу.

5 голосов
/ 15 февраля 2012

Он считается функциональным, если ваш оператор if имеет возвращаемое значение и не имеет побочных эффектов.

Предположим, вы хотите написать эквивалент:

if(x > 3) n = 3; else n = x;

Вместо этого вы используете оператор return из команды if:

let n = (if x > 3 then 3 else x)

Эта гипотетическая if внезапно становится функциональной, поскольку не имеет побочных эффектов; это только возвращает значение. Думайте об этом, как будто это был троичный оператор в некоторых языках: int n = x>3?3:x;

3 голосов
/ 15 февраля 2012

Есть два наблюдения, которые могут помочь в переходе от императивного к функциональному («все есть выражение») программированию:

  1. unit - это значение, тогда как выражение, возвращающее void в C #, рассматривается как оператор. То есть C # делает различие между утверждениями и выражениями. В F # все является выражением.

  2. В C # значения можно игнорировать; в F # они не могут, поэтому обеспечивают более высокий уровень безопасности типов. Эта четкость делает программы на F # более легкими для рассуждения и дает большие гарантии.

1 голос
/ 11 июля 2016

Приношу свои извинения за то, что не знал F #, но вот одно из возможных решений в javascript:

function $if(param) {
    return new Condition(param)
}

function Condition(IF, THEN, ELSE) {
    this.call = function(seq) {
        if(this.lastCond != undefined) 
            return this.lastCond.call(
                sequence(
                    this.if, 
                    this.then, 
                    this.else, 
                    (this.elsif ? this.elsif.if : undefined),
                    seq || undefined
                )
            );
         else 
            return sequence(
                this.if, 
                this.then, 
                this.else, 
                (this.elsif ? this.elsif.if : undefined),
                seq || undefined
            )
    }


    this.if   = IF ? IF : f => { this.if = f; return this };
    this.then = THEN ? THEN : f => { this.then = f; return this };
    this.else = ELSE ? ELSE : f => { this.else = f; return this };
    this.elsif = f => {this.elsif = $if(f); this.elsif.lastCond = this; return this.elsif};
}

function sequence(IF, THEN, ELSE, ELSIF, FINALLY) {
    return function(val) {
        if( IF(val) ) 
            return THEN();

        else if( ELSIF && ELSIF(val) ) 
            return FINALLY(val);

        else if( ELSE ) 
            return ELSE();

        else 
            return undefined

    }
}}

Функция $ if возвращает объект с конструкцией if..then..else..elsif, используяУсловие конструктора.Как только вы вызовете Condition.elsif (), вы создадите еще один объект Condition - по сути, создадите связанный список, который можно рекурсивно просмотреть с помощью sequence ()

. Вы можете использовать его следующим образом:

var eq = val => x => x == val ? true : false;

$if( eq(128) ).then( doStuff ).else( doStuff )
.elsif( eq(255) ).then( doStuff ).else( doStuff ).call();

Однако я понимаю, что использование объекта не является чисто функциональным подходом.Итак, в этом случае вы можете отказаться от объекта все вместе:

sequence(f, f, f, f,
    sequence(f, f, f, f
        sequence(f, f, f)
    )
);

Вы можете видеть, что магия действительно в функции sequence ().Я не буду пытаться ответить на ваш конкретный вопрос в JavaScript.Но я думаю, что главное заключается в том, что вы должны создать функцию, которая будет запускать произвольные функции в операторе if..then, а затем вложить кратные значения этой функции, используя рекурсию, для создания сложной логической цепочки.По крайней мере, так вам не придется повторяться;)

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

...