我一直在学习F#和函数式编程,并试图以功能的方式做事。但是,当我重写一些我已用C#编写的代码时,我会陷入简单的if-then语句(只做一些事情而不返回值)。我知道你可以在F#中解决这个问题:
if expr then do ()
但是,我认为这是编码的必要方法?也许我对功能编程学得不够,但它对我来说似乎不起作用。我认为函数式方法是组合函数和表达式,而不是简单地一个接一个地执行语句,这就是if-then似乎鼓励的。
所以,我错过了什么,如果 - 那么在功能世界中是完美的吗?如果没有,这种陈述的功能等同物是什么?我怎么能拿一个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#相当容易,但是我很难将其转换为功能。每次我在C#中使用if语句,并且我正在尝试将其传输到F#时,我感到气馁的是我无法想出一种方法来使代码更具功能性。
答案 0 :(得分:35)
到目前为止尚未提及的重要一点是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
- 返回分支。
您可以使用各种调整,例如使用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
。
答案 1 :(得分:13)
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作为陈述是一种必要的思维方式。根据我的经验,函数式编程更多地是关于思维方式而不是编程语言中的具体控制流程。
修改强>
您的代码完全可读。一些小问题是摆脱多余的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()
答案 2 :(得分:8)
if-expression不是if-expression,而是if-expression中的内容。例如,let abs num = if num < 0 then -num else num
是编写abs函数的完全功能的方法。它接受一个参数并返回该参数的变换而没有副作用。但是当你有“只做某事的代码,而不是返回一个值”时,然后你正在写一些不是纯粹功能的东西。函数式编程的目标是最小化程序中可以用这种方式描述的部分。你如何写你的条件是切线。
答案 3 :(得分:7)
为了编写复杂的代码,您需要在某个时刻进行分支。您可以使用非常有限的方法,并且所有这些方法都需要通过一段代码的逻辑流程。如果你想避免使用if / then / else,可能会使用loop / while / repeat作弊 - 但这会使你的代码维护和阅读的合理性降低。
函数式编程并不意味着你不应该一个接一个地执行语句 - 它只是意味着你不应该有一个可变状态。每次调用时,每个函数都需要以相同的方式可靠地运行。数据处理方式的任何差异都需要通过传入的数据来计算,而不是从调用函数的任何内容中隐藏的某些触发器。
例如,如果我们有一个函数foo(int, bool)
根据bool
是真还是假来返回不同的函数,那么{{1}中几乎肯定会有if
语句}}。那是完全合法的。不合法的是使函数foo()
返回不同的函数,具体取决于它是否是第一次在程序中调用它。这是一个“有状态”的功能,它使任何维持该计划的人都难以生活。
答案 4 :(得分:5)
如果您的if
语句具有返回值并且没有副作用,则认为它是有效的。
假设你想写相当于:
if(x > 3) n = 3; else n = x;
您可以使用if命令中的return语句:
,而不是这样做 let n = (if x > 3 then 3 else x)
这个假设的if
突然起作用,因为它没有副作用;它只返回一个值。可以把它想象成某些语言中的三元运算符:int n = x>3?3:x;
答案 5 :(得分:3)
有两个观察可以帮助从命令式转变为功能性(“一切都是表达式”)编程:
unit
是一个值,而在C#中返回void
的表达式被视为一个语句。也就是说,C#区分了语句和表达式。在F#中,所有都是表达式。
在C#中可以忽略值;在F#中它们不能,因此提供更高级别的类型安全性。这种显性使F#程序更易于推理并提供更好的保证。
答案 6 :(得分:1)
我对不知道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函数使用Condition构造函数返回一个带有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语句下运行任意函数的函数,然后使用递归来嵌套该函数的多个来创建一个复杂的逻辑链。至少这种方式你不必重复自己;)
此代码只是一个原型,所以请将其作为概念证明,如果您发现任何错误,请告诉我。