Какая функциональная замена для операторов if-then?
Я изучаю F # и функциональное программирование и стараюсь делать что-то функциональным способом. Однако, когда дело доходит до перезаписи кода, который я уже написал на С#, я застрял в простых операциях 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 #, довольно легко просто переносить мой С# напрямую, но у меня возникают трудности с его функциональностью. Каждый раз, когда я получаю такой оператор if на С#, и я пытаюсь перенести его на F #, я смущаюсь, что не могу придумать, как сделать код более функциональным.
Ответы
Ответ 1
Важным моментом, который до сих пор не упоминался, является разница между if .. then .. else
и if .. then
без ветвления else
.
Если в функциональных языках
Функциональная интерпретация if
заключается в том, что она является выражением, которое оценивает некоторое значение. Чтобы оценить значение if c then e1 else e2
, вы оцениваете условие c
, а затем оцениваете либо e1
, либо e2
, в зависимости от условия. Это дает результат if .. then .. else
.
Если у вас есть только if c then e
, то вы не знаете, каков будет результат оценки, если c
is 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
-прямой веткой.
Вы можете использовать различные настройки, например, представлять свои данные, используя 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
самостоятельно. BTW: Я также рекомендую использовать use
, если вы хотите Dispose
объект автоматически.
Ответ 2
Нет ничего плохого в if-then в функциональном мире.
Ваш пример действительно похож на let _ = expr
, поскольку expr
имеет побочные эффекты, и мы игнорируем его возвращаемое значение. Более интересный пример:
if cond then expr
что эквивалентно:
match cond with
| true -> expr
| false -> ()
если мы используем сопоставление шаблонов.
Когда условие простое или существует только одно условное выражение, if-then более читаемо, чем сопоставление шаблонов. Более того, стоит отметить, что все в функциональном программировании является выражением. Поэтому if cond then expr
на самом деле является ярлыком if cond then expr else ()
.
Если-то сама по себе не является императивной, используя if-then, поскольку утверждение является императивным способом мышления. По моему опыту, функциональное программирование - это скорее образ мышления, чем конкретные потоки управления в языках программирования.
EDIT:
Ваш код полностью читаем. Некоторые второстепенные моменты избавляются от избыточного ключевого слова 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()
Ответ 3
Чтобы написать сложный код, вам нужно разветкиться в какой-то момент. Там очень ограниченное количество способов сделать это, и все они требуют логического потока через раздел кода. Если вы хотите избежать использования if/then/else, вы можете обмануть с помощью цикла /while/repeat, но это сделает ваш код намного менее разумным для обслуживания и чтения.
Функциональное программирование не означает, что вы не должны выполнять инструкции один за другим - это просто означает, что вы не должны иметь изменяемое состояние. Каждая функция должна надежно вести себя одинаково каждый раз, когда она вызывается. Любые различия в том, как данные обрабатываются им, должны учитываться данными, которые передаются, вместо какого-либо триггера, который скрыт от того, что вызывает функцию.
Например, если у нас есть функция foo(int, bool)
, которая возвращает что-то другое в зависимости от того, является ли bool
true или false, почти наверняка будет оператор if
где-то в foo()
. Это совершенно законно. То, что НЕ является законным, состоит в том, чтобы иметь функцию foo(int)
, которая возвращает что-то другое в зависимости от того, будет ли она впервые вызвана в программе. Это функция "с точки зрения состояния", и это затрудняет жизнь любому, кто поддерживает программу.
Ответ 4
Это не неотрицательное if-выражение, это то, что происходит в if-выражении. Например, let abs num = if num < 0 then -num else num
- полностью функциональный способ записи функции abs. Он принимает аргумент и возвращает преобразование этого аргумента без побочных эффектов. Но когда у вас есть "код, который только что-то делает, а не возвращает значение", тогда вы пишете что-то, что не является чисто функциональным. Цель функционального программирования - минимизировать часть вашей программы, которая может быть описана именно так. Как вы пишете свои условности, тангенциально.
Ответ 5
Есть два наблюдения, которые могут помочь в переходе от императивного к функциональному ( "все является выражением" ) программирования:
-
unit
- это значение, тогда как выражение, возвращающее void
в С#, рассматривается как оператор. То есть, С# делает различие между операторами и выражениями. В F # все это выражение.
-
В значениях С# можно игнорировать; в F # они не могут, поэтому обеспечивают более высокий уровень безопасности типов. Эта ясность делает программы F # более понятными и предоставляет большие гарантии.
Ответ 6
Он считается функциональным, если ваш оператор 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;
Ответ 7
Мои извинения за незнание 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.
После вызова Condition.elsif() вы создадите другой объект Condition - по существу создаете связанный список, который может быть рекурсивно пройден с использованием последовательности()
Вы можете использовать его как:
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, а затем вложить кратность этой функции с помощью рекурсии для создания сложной логической цепочки. По крайней мере, таким образом вам не придется повторять себя;)
Этот код является всего лишь прототипом, поэтому, пожалуйста, возьмите его как доказательство концепции и дайте мне знать, если найдете какие-либо ошибки.