如何解决F#在函数中返回
我是 F# 的新手并且玩过一点,我已经使用过 C#。最近让我有点困惑的一件事是在函数中返回值。我不明白一切都是一个表达式(如 here 所述)并且以下代码不会编译或执行:
function validateTextProps(props: GenericProps): props is InputProps<TextState> {
return props.state.type === Types.TEXT;
}
const props: GenericProps = { state: { type: Types.TEXT } };
if(validateTextProps(props)){
TextInput(props);
}
相反,你必须这样写:
let foo x =
if x = 0 then x + 2 //The compiler will complain here due to a missing else branch.
x - 2
我确实明白为什么会这样,并且在循环时也会出现类似的问题。
但是考虑一个函数,它等于 C# 中列表的“包含”函数:
let foo x =
if x = 0 then x + 2
else x - 2
如果您将其一一翻译,编译器会告诉您缺少 else 分支,我确实不明白为什么:
public static bool Contains(int num,List<int> list) {
foreach (int i in list) {
if (i == num) return true;
}
return false;
}
相反,您可能会编写这样的递归函数:
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
我对此没有意见,但在某些情况下,很难进行模式匹配。真的没有办法做到上面描述的吗?难道不能使用像 yield 这样的关键字(或类似于 C# 中的“return”)?
解决方法
我同意你的看法:在 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# →
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
,
请记住,F# 编译器将尾递归转换为循环。因此,解决方案实际上是使用尾递归辅助函数,即使您使用的类型不具有 F# 列表的模式匹配支持。一些例子:
一个包含任何 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
等库函数。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。