专家f#脚本编译怪

  • 本文关键字:编译 脚本 专家 f#
  • 更新时间 :
  • 英文 :


在第209-210页上有一个扩展示例(见下文((我使用的是F#4.5(

总之,我不明白的是:如果我单独键入每个语句,就会有一个声明引发错误。如果我一次提交整个脚本,以及引发错误的声明之后的函数,一切都可以。那么,当我批量提交所有声明时,在交互中会发生什么呢?错误后的函数是否用于创建潜在泛型的特定版本?ionide提示没有显示整个脚本的泛型。

详细说明(抱歉太长了(如果您在交互式中按顺序执行每个语句,直到并包括formatP的声明,(或一次将所有语句粘贴到并包括formatP,例如在vs代码中选择并用ionide交替输入(,则会产生错误:

binary.fsx(67,5): error FS0030: Value restriction. The value 'formatP' has been inferred to have generic type
val formatP : ((int * bool) list -> '_a -> unit) when '_a :> OutState
Either make the arguments to 'formatP' explicit or, if you do not intend for it to be generic, add a type annotation.
  • 如果粘贴所有脚本,包括formatP之后的函数
let writeData file data =
use outStream = BinaryWriter(File.OpenWrite(file))
formatP data outStream
let readData file =
use inStream = BinaryReader(File.OpenRead(file))
formatU inStream

它工作时不会出错。

考虑到编译顺序很重要,这里发生了什么

即为什么在CCD_ 4之后声明的函数允许该函数编译。

整个脚本-运行时没有错误(它有点长-但因为我真的不理解这个问题,我不确定我可以删除什么来创建一个准确的示例来说明正在发生的事情。我想我可以放弃formatUreadData?(具有相同的关系(,但它们是使最后两个雄蕊工作所必需的,这表明代码确实按照预期编译和执行(:

open System.IO
type OutState = BinaryWriter
type InState = BinaryReader
type Pickler<'T> = 'T -> OutState -> unit
type Unpickler<'T> = InState -> 'T
// P is the suffix for pickling and U is the suffix for unpickling
let byteP (b : byte) (st : OutState) = st.Write(b)
let byteU (st : InState) = st.ReadByte()
let boolP b st = byteP (if b then 1uy else 0uy) st
let boolU st = let b = byteU st in (b = 1uy)
let int32P i st =
byteP (byte (i &&& 0xFF)) st
byteP (byte ((i >>> 8) &&& 0xFF)) st
byteP (byte ((i >>> 16) &&& 0xFF)) st
byteP (byte ((i >>> 24) &&& 0xFF)) st
let int32U st =
let b0 = int (byteU st)
let b1 = int (byteU st)
let b2 = int (byteU st)
let b3 = int (byteU st)
b0 ||| (b1 <<< 8) ||| (b2 <<< 16) ||| (b3 <<< 24)
let tup2P p1 p2 (a, b) (st : OutState) =
(p1 a st : unit)
(p2 b st : unit)
let tup3P p1 p2 p3 (a, b, c) (st : OutState) =
(p1 a st : unit)
(p2 b st : unit)
(p3 c st : unit)
let tup2U p1 p2 (st : InState) =
let a = p1 st
let b = p2 st
(a, b)
let tup3U p1 p2 p3 (st : InState) =
let a = p1 st
let b = p2 st
let c = p3 st
(a, b, c)
/// Outputs a list into the given output stream by pickling each element via f.
/// A zero indicates the end of a list, a 1 indicates another element of a list.
let rec listP f lst st =
match lst with
| [] -> byteP 0uy st
| h :: t -> byteP 1uy st; f h st; listP f t st
// Reads a list from a given input stream by unpickling each element via f.
let listU f st =
let rec loop acc =
let tag = byteU st
match tag with
| 0uy -> List.rev acc
| 1uy -> let a = f st in loop (a :: acc)
| n -> failwithf "listU: found number %d" n
loop []
type format = list<int32 * bool>
//the types in these two lines only get fully inferred if the lines after are part of the batch
//eh?
let formatP = listP (tup2P int32P boolP)
let formatU = listU (tup2U int32U boolU)
//IE if you only run to here it is an error
let writeData file data =
use outStream = BinaryWriter(File.OpenWrite(file))
formatP data outStream
let readData file =
use inStream = BinaryReader(File.OpenRead(file))
formatU inStream
//If you run to here it does not error
writeData "out.bin" [(102, true); (108, false)] ;;
readData "out.bin"

这里有两件事,我只能完全解释其中一件。

第一个是F#值限制。在这种情况下,编译器推断listP是一个泛型函数,这也使值formatP具有泛型。但是F#在这种情况下不允许使用泛型值,因此会发生编译器错误。但是,当存在writeData时,formatP不再是泛型,因此编译器错误消失。你可以看到一个非常相似的情况在这里详细解释。

但是,为什么编译器首先认为listP是通用的呢?我对此不确定。它推断st必须与OutState兼容,从面向对象的角度来看这是正确的。但它将其表示为一个通用约束,使用st : #OutState而不仅仅是st : OutState。(F#称之为"灵活类型"。(然后,这个通用约束级联到formatP,导致您报告的问题。对F#编译器有更多了解的人可能能够解释这一点。


这里有一个表现出相同行为的非常小的例子:

type Example() = class end
let alpha (_ : Example) =
()
let beta f x =
alpha (f x)
let gamma = beta id
// let delta = gamma (Example())

编译器推断出beta的泛型类型,这使得当delta被注释掉时gamma是非法的。

更奇怪的是,即使我明确地注释了beta,也会出现同样的问题,所以它不是通用的:

let beta (f : Example -> Example) (x : Example) =
alpha (f x)

所以编译器认为gamma是泛型的,即使beta肯定不是泛型的。我不知道为什么。

最新更新