F#中的(..)和(.. ..)运算符在某些时候展开,是编译时操作还是运行时操作?
在任何一种情况下,这有什么表现?即是否可以构建一个更快地完成这些操作的自定义函数?
答案 0 :(得分:3)
运行时间。作为编译的一部分,F#很少会运行您的代码 - 我能想到的唯一情况是NumericLiteralX
模块中的代码。此外,在这样的代码中:
let f n = [1 .. n]
在编译时甚至不知道上限。当然,作为一个实现细节,F#编译器可能显式地展开定义,其中两个边界都是已知类型的静态已知值(例如int
),但在语义上它应该始终与在运行时完成的相同时间。
关于你的第二个问题,比什么更快?
答案 1 :(得分:3)
我认为来自 kvb 的回复回答了大多数问题。但是,我认为更准确的答案是范围在运行时懒惰地评估。以下是范围如何工作的更多细节......
当您在代码中的某处使用1 .. 10
时,它只是转换为某个方法调用。调用取决于使用的上下文和数字类型。
对于[ 1 .. 10 ]
或其他序列表达式和for
循环,编译器将生成类似RangeInt32(1, 1, 10)
的内容(附加参数是步骤)。
如果你有obj.[ 1 .. ]
和obj
这样的东西是支持切片的对象(例如矩阵类型),那么它将被翻译为obj.GetSlice(Some(1), None)
(请注意,在此如果上限/下限可能丢失了。
现在很容易回答你的问题 - 这是一个将在运行时进行评估的方法调用。然而,重要的是要注意整个范围可能不需要评估!例如:
let nums = seq { 1 .. 10 } |> Seq.take 1
序列表达式将转换为对RangeInt32
的调用。这将只返回一个懒惰评估的seq<int>
类型的值。对take 1
的调用仅采用第一个元素,因此只需要该范围中的第一个数字并进行评估。
我认为您自己的范围实现可能与标准实现不同,但您可以将您的实现作为对象的成员提供。然后你可以写myObj.[1 .. 10]
(结果可以是你想要的任何类型)。为此,您需要一个实例方法GetSlice
,其中更详细discussed here。