F# return 在函数中
F# return in functions
我是 F# 的新手并且玩了一会儿,我已经使用过 C#。最近让我有点困惑的一件事是 returning 函数中的值。我不明白一切都是表达式(如 here 所述)并且以下代码不会编译也不会执行:
let foo x =
if x = 0 then x + 2 //The compiler will complain here due to a missing else branch.
x - 2
相反,您必须这样写:
let foo x =
if x = 0 then x + 2
else x - 2
我明白为什么会这样,并且在循环时也会出现类似的问题。
但是考虑一个函数,它等于 C# 中列表的“包含”函数:
public static bool Contains(int num, List<int> list) {
foreach (int i in list) {
if (i == num) return true;
}
return false;
}
如果你将那个一个一个地翻译,编译器会告诉你缺少一个 else 分支,我不明白为什么:
let Contains (num : int) list =
for i in list do
if i = x then true //Won't compile due to a missing else branch!
false
相反,您可能会编写这样的递归函数:
let rec Contains (num : int) list =
match list with
| [] -> false
| head::tail -> head = num || (Contains num tail)
我对此没有意见,但在某些情况下,很难进行模式匹配。真的没有办法做到上述吗?不能使用像 yield 这样的关键字(或类似于 C# 中的“return”的关键字)吗?
C# 中的早期 return
实际上只是一个 GOTO。我会认为这种糟糕的风格,除了“保镖模式”(验证)。那里的争论通常是尽早摆脱精神超负荷。鉴于 F# 的表现力,大多数验证都可以通过类型进行,因此不需要(或至少不需要)无处不在的 if param = null || invalid(param) return
样式。一旦您接受了功能性风格,您会发现对它的需求越来越少。如果需要,您可以随时模拟 goto :-)
exception Ret of string
let needsEarlyReturn foos =
try
for f in foos do
if f > 42 then Ret "don't do" |> raise
// more code
if f > 7 then Ret "that ever" |> raise
"!"
with Ret v -> v
我同意你的看法:在 C# 中提前退出可能很方便,尤其是通过避免嵌套 if
.
来提高可读性
在 F# 中,为了避免嵌套 if
或太大的模式匹配(即情况太多或输入元组包含太多项),我们可以提取子函数:使用有意义的名称,它改进代码可读性。
对于 C# 中的列表和序列 (IEnumerable
),使用内置方法或 LINQ 的代码通常比使用 for/foreach
循环更具声明性。这并不总是正确的,但值得一试。顺便说一下,在某些情况下,ReSharper 可以建议将 for/foreach
循环重构为 LINQ 查询。
→ 这种编码风格很容易翻译成 F#:
- 名单:
- C# →
list.Contains(num)
- F# →
[1;2;3] |> List.contains 1
- 序列:
- C# →
list.Any(x => x == num)
- F# →
seq { 1;2;3 } |> Seq.exists ((=) 1)
- F# →
seq { 1;2;3 } |> Seq.contains 1
请记住,F# 编译器会将尾递归转换为循环。因此,解决方案实际上是使用尾递归辅助函数,即使您使用的类型不具有 F# 列表的模式匹配支持。一些例子:
A 包含任何 IEnumerable<int>
:
的方法
let contains item (xs : int seq) =
use e = xs.GetEnumerator()
let rec helper() = e.MoveNext() && (e.Current = item || helper())
helper()
System.Collections.Generic.List<int>
的类似方法:
let contains item (xs : ResizeArray<int>) =
let rec helper i = i < xs.Count && (xs.[i] = item || helper (i + 1))
helper 0
一般来说,您可以像这样使用一个辅助函数来提前退出(为了使示例简单,循环是无限的;唯一的出路是通过提前退出):
let shortCircuitLoop shouldReturn getNextValue seed =
let rec helper accumulator =
if shouldReturn accumulator then accumulator else helper (getNextValue accumulator)
helper seed
此的 C# 版本:
T ShortCircuit<T>(Func<T, bool> shouldReturn, Func<T, T> getNextValue, T seed) {
while (true)
{
if (shouldReturn(seed))
return seed;
seed = getNextValue(seed);
}
}
当然,这些示例仅供参考,作为教学练习。在生产代码中,如 Romain Deneau 所述,您需要使用 Seq.contains
、List.contains
等库函数。
我是 F# 的新手并且玩了一会儿,我已经使用过 C#。最近让我有点困惑的一件事是 returning 函数中的值。我不明白一切都是表达式(如 here 所述)并且以下代码不会编译也不会执行:
let foo x =
if x = 0 then x + 2 //The compiler will complain here due to a missing else branch.
x - 2
相反,您必须这样写:
let foo x =
if x = 0 then x + 2
else x - 2
我明白为什么会这样,并且在循环时也会出现类似的问题。
但是考虑一个函数,它等于 C# 中列表的“包含”函数:
public static bool Contains(int num, List<int> list) {
foreach (int i in list) {
if (i == num) return true;
}
return false;
}
如果你将那个一个一个地翻译,编译器会告诉你缺少一个 else 分支,我不明白为什么:
let Contains (num : int) list =
for i in list do
if i = x then true //Won't compile due to a missing else branch!
false
相反,您可能会编写这样的递归函数:
let rec Contains (num : int) list =
match list with
| [] -> false
| head::tail -> head = num || (Contains num tail)
我对此没有意见,但在某些情况下,很难进行模式匹配。真的没有办法做到上述吗?不能使用像 yield 这样的关键字(或类似于 C# 中的“return”的关键字)吗?
C# 中的早期 return
实际上只是一个 GOTO。我会认为这种糟糕的风格,除了“保镖模式”(验证)。那里的争论通常是尽早摆脱精神超负荷。鉴于 F# 的表现力,大多数验证都可以通过类型进行,因此不需要(或至少不需要)无处不在的 if param = null || invalid(param) return
样式。一旦您接受了功能性风格,您会发现对它的需求越来越少。如果需要,您可以随时模拟 goto :-)
exception Ret of string
let needsEarlyReturn foos =
try
for f in foos do
if f > 42 then Ret "don't do" |> raise
// more code
if f > 7 then Ret "that ever" |> raise
"!"
with Ret v -> v
我同意你的看法:在 C# 中提前退出可能很方便,尤其是通过避免嵌套 if
.
在 F# 中,为了避免嵌套 if
或太大的模式匹配(即情况太多或输入元组包含太多项),我们可以提取子函数:使用有意义的名称,它改进代码可读性。
对于 C# 中的列表和序列 (IEnumerable
),使用内置方法或 LINQ 的代码通常比使用 for/foreach
循环更具声明性。这并不总是正确的,但值得一试。顺便说一下,在某些情况下,ReSharper 可以建议将 for/foreach
循环重构为 LINQ 查询。
→ 这种编码风格很容易翻译成 F#:
- 名单:
- C# →
list.Contains(num)
- F# →
[1;2;3] |> List.contains 1
- C# →
- 序列:
- C# →
list.Any(x => x == num)
- F# →
seq { 1;2;3 } |> Seq.exists ((=) 1)
- F# →
seq { 1;2;3 } |> Seq.contains 1
- C# →
请记住,F# 编译器会将尾递归转换为循环。因此,解决方案实际上是使用尾递归辅助函数,即使您使用的类型不具有 F# 列表的模式匹配支持。一些例子:
A 包含任何 IEnumerable<int>
:
let contains item (xs : int seq) =
use e = xs.GetEnumerator()
let rec helper() = e.MoveNext() && (e.Current = item || helper())
helper()
System.Collections.Generic.List<int>
的类似方法:
let contains item (xs : ResizeArray<int>) =
let rec helper i = i < xs.Count && (xs.[i] = item || helper (i + 1))
helper 0
一般来说,您可以像这样使用一个辅助函数来提前退出(为了使示例简单,循环是无限的;唯一的出路是通过提前退出):
let shortCircuitLoop shouldReturn getNextValue seed =
let rec helper accumulator =
if shouldReturn accumulator then accumulator else helper (getNextValue accumulator)
helper seed
此的 C# 版本:
T ShortCircuit<T>(Func<T, bool> shouldReturn, Func<T, T> getNextValue, T seed) {
while (true)
{
if (shouldReturn(seed))
return seed;
seed = getNextValue(seed);
}
}
当然,这些示例仅供参考,作为教学练习。在生产代码中,如 Romain Deneau 所述,您需要使用 Seq.contains
、List.contains
等库函数。