F# computational expression缩进差异引发FS3118/FS0588报错咨询
F# 计算表达式缩进规则问题解析
这个编译错误确实由F#的缩进(越位)规则与计算表达式的块结构共同触发,和链式调用的位置直接相关。
核心规则说明
F#采用基于缩进的越位规则判定代码块边界,不需要靠分号、显式块结束标记断句,和当前问题直接相关的规则有三条:
- 所有开启新代码块的语法结构(包括
for ... do、let ... =、计算表达式的{),都会以结构起始token的列位置作为基准:块内代码的起始列必须大于基准列,一旦出现起始列小于等于基准列的token,编译器就会判定当前块结束。 - 块内的
let绑定完成后,必须跟随一个和let起始列对齐的表达式作为块的返回值,否则就会触发FS0588错误。 - 对于带闭合界定符的表达式(比如计算表达式的
{ ... }、列表[ ... ]、括号( ... )),如果闭合界定符后要接链式运算符(比如|>、.成员访问),整个链式调用的缩进必须落在该表达式所属的层级(即缩进深度大于该表达式赋值目标的let基准列),否则编译器不会把链式调用识别为该表达式的一部分。
报错代码的问题
对比可正常编译的版本,报错版本的唯一改动是把|> Map.ofSeq从元组内移到了内层seq表达式的闭合}后面,但是缩进没有调整:
内层seq { ... }的闭合}和外层let attLookup的let关键字同列,编译器遇到这个}时,会判定内层seq块已经结束,但同行的|> Map.ofSeq和let同列,缩进深度和let基准列持平,不会被识别为seq表达式的后续链式调用,反而被判定为let绑定之后的独立代码。这时候编译器会认为let attLookup = seq { ... }的绑定没有完成(等号右侧的表达式没有被完整识别),同时let绑定之后没有合法的对齐返回表达式,因此同时抛出FS3118和FS0588两个错误。
修正方案
共有三种符合缩进规则的合法写法:
方案1:将等号右侧的整个表达式(seq+链式调用)缩进一层
把seq { ... } |> Map.ofSeq整体缩进到比let更深的层级,明确告诉编译器这部分全是等号右侧的绑定值:
seq { for concept in model.EsWondomainmodelconcepts do let attLookup = seq { for att in concept.Attributes.EsWondomainmodelattributes do for tag in (att.XmlTag |> Option.toArray) do for mult in (att.Multiplicity.EspEnumeration.Identificationdisplaystring.String |> Option.toArray) do (tag,multiplicityToRelationshipCardinality mult) } |> Map.ofSeq (concept.XmlTag, attLookup) } |> Map.ofSeq |> WOnSchemaRawData
方案2:换行放置链式调用,对齐seq表达式的起始列
闭合seq块的}单独占一行对齐seq关键字,后续|> Map.ofSeq换行后和seq同缩进,作为seq表达式的续行:
seq { for concept in model.EsWondomainmodelconcepts do let attLookup = seq { for att in concept.Attributes.EsWondomainmodelattributes do for tag in (att.XmlTag |> Option.toArray) do for mult in (att.Multiplicity.EspEnumeration.Identificationdisplaystring.String |> Option.toArray) do (tag,multiplicityToRelationshipCardinality mult) } |> Map.ofSeq (concept.XmlTag, attLookup) } |> Map.ofSeq |> WOnSchemaRawData
方案3:保留原有可编译版本的写法
也就是不在let绑定阶段调用Map.ofSeq,而是在后续组装元组时对attLookup做转换,这种写法已经符合缩进要求,可以正常编译。
内容的提问来源于stack exchange,提问作者MrD at KookerellaLtd
相关产品推荐
相关产品推荐

