为什么 F# 无法对列表元素进行类型推断?

Why F# fails to have type inference for list elements?

I was trying to put both int and float into a list construction:

> let l2=[1;2.0];;

  let l2=[1;2.0];;
  ----------^^^

stdin(50,11): error FS0001: This expression was expected to have type
    int
but here has type
    float
>

好吧,如果我在 Haskell 中写入 let l3=[1,2.0],它可以构建包含所有 "fractional" 元素的列表。为什么 F# 不支持自动类型推断? F#是基于.net的,Int和Float都是Object类型吧?为什么 Int 和 Float 不能放在一个列表中,而有些元素会自动进行类型提升或转换?似乎 F# 的类型系统使得编写更通用的程序变得非常困难。

有什么提示吗?

F# 没有像 Haskell 这样的类型类。虽然它基于 .NET,但 intfloat 之间唯一的共同类型是 obj,并且 obj 不提供使您能够执行算术的运算符。

不过,您可以定义一个包含intfloat值的列表:

let l : obj list = [1; 2.0]

不过,这接近于 无用,因为除非您尝试向下转换它们,否则您无法使用这些值任何事情。它也是 不安全的 ,因为这样的列表很容易包含非数字值:

let l : obj list = [1; 2.0; "foo"]

然而,一切并没有丢失,因为您可以使用 inline 关键字让编译器找出各种输入所需的运算符:

type Direction = Left = -1 | Straight = 0 | Right = 1

let inline turn (x1, y1) (x2, y2) (x3, y3) =
    let prod = (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1)
    if prod > LanguagePrimitives.GenericZero then Direction.Left
    elif prod < LanguagePrimitives.GenericZero then Direction.Right
    else Direction.Straight

此函数具有以下类型:

val inline turn :
  x1: ^a * y1: ^f -> x2: ^b * y2: ^g -> x3: ^j * y3: ^e -> Direction
    when ( ^b or  ^a) : (static member ( - ) :  ^b *  ^a ->  ^c) and
         ( ^j or  ^a) : (static member ( - ) :  ^j *  ^a ->  ^i) and
         ( ^c or  ^d) : (static member ( * ) :  ^c *  ^d ->  ^l) and
         ( ^e or  ^f) : (static member ( - ) :  ^e *  ^f ->  ^d) and
         ( ^g or  ^f) : (static member ( - ) :  ^g *  ^f ->  ^h) and
         ( ^h or  ^i) : (static member ( * ) :  ^h *  ^i ->  ^k) and
         ( ^l or  ^k) : (static member ( - ) :  ^l *  ^k ->  ^m) and
          ^m : (static member get_Zero : ->  ^m) and  ^m : comparison

它声明 ^a^b 是支持静态运算符 - 的类型,当使用时,returns 类型 ^c,支持静态运算符*等。

这个例子来自my convex hull example.

它可以与 intfloat 列表一起使用,因为这两种类型都支持所需的运算符:

> turn (1,2) (3,3) (2,1);;
val it : Direction = Right
> turn (1.0,2.0) (3.0,3.0) (2.0,1.0);;
val it : Direction = Right

虽然不如Haskell那么优雅,但可以在紧要关头使用。但在实践中,我并不太想念它。在我的职业生涯中,我很少错过 generic arithmetic;我发现它说明了一些商业上最成功的编程语言(Java、C#)不支持泛型算术。