Troubleshooting F# 解决疑难杂症
https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/troubleshooting-fsharp/
一般来说,“如果它能编译,就说明它是正确的”,但仅仅是试图让代码编译,就会让人感到非常沮丧!因此,本页面专门帮助你解决F#代码的故障!因此,本页面致力于帮助你排除F#代码的故障。
我将首先介绍一些关于故障排除的一般建议和初学者最常犯的一些错误。之后,我将详细描述每一个常见的错误信息,并举例说明它们如何发生以及如何纠正。
故障排除的一般方法
到目前为止,你能做的最重要的事情就是花时间和精力去准确理解F#的工作原理,尤其是涉及函数和类型系统的核心概念。因此,请阅读和重读“从函数式的方式思考”和“理解F#类型”系列,运行一下这些例子,在你试图开始进行认真的编码之前,适应这些思考方式。如果你不理解函数和类型是如何工作的,那么编译器的错误就不会有任何意义。(TODO:添加链接)
如果你以前主要使用 C# 这样的命令式语言,你可能已经养成了一些坏习惯,依赖调试器来发现和修复不正确的代码。在F#中,你可能不会走到那一步,因为编译器在很多方面都要严格得多。当然,也没有任何工具可以“调试”编译器并逐步完成其处理过程。调试编译器错误的最好工具是你的大脑,而F#迫使你使用它!
尽管如此,初学者还是会犯一些极为常见的错误,我将简单介绍一下这些错误。
调用函数时不要使用括号
在 F# 中,空格是函数参数的标准分隔符。您几乎不需要使用括号,尤其是在调用函数时不要使用括号。
let add x y = x + y
let result = add (1 2) // 错误
// error FS0003: This value is not a function and cannot be applied
let result = add 1 2 // 正确不要把有多个参数的元组混在一起当成多个参数来使用
如果一对括号中间有至少一个逗号,它就是一个元组。而一个元组是一个对象而不是两个。所以你会得到关于传递错误的参数类型的错误,或者参数太少。
addTwoParams (1,2) // trying to pass a single tuple rather than two args
// error FS0001: This expression was expected to have type
// int but here has type 'a * 'b编译器将 (1,2) 视为泛型元组,并尝试将其传递给 “addTwoParams”。然后它报错说 addTwoParams 的第一个参数是一个 int,而我们正试图传递一个元组。
如果你试图将两个参数传递给一个需要一个元组的函数,你会得到另一个模糊的不容易理解的错误。这里的错误和函数运算优先级有关,由于按顺序进行函数调用(波兰表示法),所以最后一个值会被前面的结果作为函数的值进行调用,但如果第一个函数本身不是柯里化返回的,那就会出现使用值作为函数再输入值的现象,这是明显错误的。
addTuple 1 2 // trying to pass two args rather than one tuple
// error FS0003: This value is not a function and cannot be applied注意参数太少或太多
如果你传递给函数的参数太少,F#编译器不会报错(事实上“partial application”是一个重要的特性),但如果你不明白发生了什么,你会经常得到奇怪的“类型不匹配”的错误。
同样地,参数过多的错误通常是 "这个值不是一个函数",而不是一个更直接的错误。(译:就是说不能把单纯的值当成函数来使用)
“printf”函数族在这方面非常严格。参数的数量必须是严格准确的。
这是一个很重要的话题?了解partial application的工作原理至关重要。有关更详细的讨论,请参阅“函数式思考”系列。(TODO:加链接)
使用分号作为 list 的分隔符
在F#需要明确分隔符的少数地方,如 list 和 record ,只使用分号,绝对不会使用逗号。(像一张破唱片一样,我将提醒你,逗号是为tuples服务的)。
let list1 = [1,2,3] // wrong! This is a ONE-element list containing
// a three-element tuple
let list1 = [1;2;3] // correct
type Customer = {Name:string, Address: string} // wrong
type Customer = {Name:string; Address: string} // correct不要使用 ! 或 != 进行不等于判断
感叹号不是“NOT”运算符。它是可变引用的引用运算符。如果你错误地使用它,你会得到如下错误:
let y = true
let z = !y
// => error FS0001: This expression was expected to have
// type 'a ref but here has type bool正确的结构是使用 "not" 关键字。想想SQL或VB的语法,而不是C的语法。
let y = true
let z = not y //correct对于“不等于”,应该使用“<>”,同样类似于 SQL 或 VB。
let z = 1 <> 2 //correct不要使用 = 进行赋值
如果你使用的是易变的值,赋值操作写成"<-"。如果你使用等号,你甚至可能不会得到一个错误,只是一个意外的结果。(译:有些时候,使用 = 会直接进行相等比较)
let mutable x = 1
x = x + 1 // returns false. x is not equal to x+1
x <- x + 1 // assigns x+1 to x注意隐藏的制表符
缩进规则非常简单,很容易掌握。但是你不能使用制表符,只能使用空格。
let add x y =
{tab}x + y
// => error FS1161: TABs are not allowed in F# code请确保将你的编辑器设置为将制表符转换为空格。如果你从其他地方粘贴代码,也要注意。如果你确实在某段代码上遇到了持续的问题,可以尝试删除空白处,然后重新添加。
不要将简单值误认为函数值
如果您正在尝试创建一个函数指针或委托,请注意不要意外创建一个已经计算过的简单值。
如果您想要一个可以重用的无参数函数,您将需要显式传递一个unit(译:就是一对空括号())参数,或将其定义为 lambda。
let reader = new System.IO.StringReader("hello")
let nextLineFn = reader.ReadLine() //wrong
let nextLineFn() = reader.ReadLine() //correct
let nextLineFn = fun() -> reader.ReadLine() //correct
let r = new System.Random()
let randomFn = r.Next() //wrong
let randomFn() = r.Next() //correct
let randomFn = fun () -> r.Next() //correct有关无参数函数的更多讨论,请参阅“函数式思考”系列。(TODO:添加链接)
解决“信息不足”错误的提示
F# 编译器目前是单向从左到右的编译器,因此如果程序中稍后的类型信息尚未被解析,编译器将无法使用它。
许多错误可能由此引起,例如 "FS0072: Lookup on object of indeterminate type" 和 "FS0041: A unique overload for could not be determined"。下面描述了针对每个特定情况的建议修复,但如果编译器抱怨缺少类型或信息不足,则有一些通用原则可以提供帮助。这些准则是:
在使用之前定义事物(这包括确保文件以正确的顺序编译)
将具有“已知类型”的对象或者变量放在具有“未知类型”的对象或者变量之前。特别的,您可以重新排序管道和类似的链式函数,以便类型化的对象排在第一位。
根据需要进行注释。一个常见的技巧是添加注释,直到一切正常,然后将它们一个一个地去掉,直到达到最低限度。
如果可能,请尽量避免注释。它不仅不美观,而且使代码更加脆弱。如果没有明确的依赖关系,更改类型会容易得多。
F# compiler errors
这部分请直接看原文,实在多,先翻译更干一些的部分
https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/troubleshooting-fsharp/#NumericErrors
(TODO:补全翻译)
Last updated