F#转换文本的惯用方式
迈洛!因此,我正在寻找一种简洁、高效、惯用的F#解析文件或字符串的方法。我强烈倾向于将输入视为字符序列(char seq)。其思想是,每个函数负责解析一段输入,返回与未使用的输入组成元组的转换文本,并由更高级别的函数调用,该函数将未使用的输入链接到以下函数,并使用结果构建复合类型。因此,每个解析函数都应该有一个类似于此函数的签名:char seq->char seq*'a。例如,如果函数的职责只是提取第一个单词,那么一种方法是:F#转换文本的惯用方式,f#,tail-recursion,F#,Tail Recursion,迈洛!因此,我正在寻找一种简洁、高效、惯用的F#解析文件或字符串的方法。我强烈倾向于将输入视为字符序列(char seq)。其思想是,每个函数负责解析一段输入,返回与未使用的输入组成元组的转换文本,并由更高级别的函数调用,该函数将未使用的输入链接到以下函数,并使用结果构建复合类型。因此,每个解析函数都应该有一个类似于此函数的签名:char seq->char seq*'a。例如,如果函数的职责只是提取第一个单词,那么一种方法是: let parseFirstWord (text: char se
let parseFirstWord (text: char seq) =
let rec forTailRecursion t acc =
let c = Seq.head t
if c = '\n' then
(t, acc)
else
forTailRecursion (Seq.skip 1 t) (c::acc)
let rest, reversedWord = forTailRecursion text []
(rest, List.reverse reversedWord)
当然,这种方法的主要问题是,它以相反的顺序提取单词,所以你必须将其反转。然而,它的主要优点是使用严格的函数特性和适当的尾部递归。在失去尾部递归的同时,可以避免提取值的反转:
let rec parseFirstWord (text: char seq) =
let c = Seq.head t
if c = '\n' then
(t, [])
else
let rest, tail = parseFirstWord (Seq.skip 1 t)
(rest, (c::tail))
或者在下面使用快速可变的数据结构,而不是使用纯粹的功能特性,例如:
let parseFirstWord (text: char seq) =
let rec forTailRecursion t queue =
let c = Seq.head t
if c = '\n' then
(t, queue)
else
forTailRecursion (Seq.skip 1 t) (queue.Enqueu(c))
forTailRecursion text (new Queue<char>())
let parseFirstWord(文本:char-seq)=
让rec fortailt递归队列=
设c=序号t
如果c='\n',则
(t,队列)
其他的
FORTAIL递归(序列跳过1T)(队列Enqueu(c))
FORTAIL递归文本(新队列())
我不知道如何在F#中使用OO概念,请注意,欢迎对上述代码进行更正
作为这门语言的新手,我希望在F#开发者通常做出的妥协方面得到指导。在建议的方法和你自己的方法中,我应该考虑更多的习惯用法,为什么?另外,在这种特殊情况下,您将如何封装返回值:char-seq*char-seq、char-seq*char-list甚至
char-seq*Queue
?或者你会考虑Char SEQ字符串在适当的转换之后吗? < P>我一定要看一下。但是,如果您只想将seq
标记化,则可以使用以正确顺序生成标记。重用递归内部函数的概念,并结合序列表达式,我们可以保持如下所示的尾部递归,并避免可变数据结构等非惯用工具
为了便于调试和函数签名,我更改了分隔符字符。这个版本会生成一个seq
(您的令牌),这可能比使用包含当前令牌和其余文本的元组更容易。如果你只想要第一个令牌,你可以拿头。请注意,序列是“按需”生成的,即输入仅在令牌通过序列消费时进行解析。如果您需要每个标记旁边的剩余输入文本,您可以在循环中生成一对,但我猜下游使用者很可能不会(此外,如果输入文本本身是一个惰性序列,可能链接到一个流,我们不想公开它,因为它应该只在一个地方迭代)
以F#特有的方式进行词法分析/解析的一种方法是使用活动模式。下面的简化示例显示了总体思路。它可以处理任意长度的计算字符串,而不会产生堆栈溢出
let rec (|CharOf|_|) set = function
| c :: rest when Set.contains c set -> Some(c, rest)
| ' ' :: CharOf set (c, rest) -> Some(c, rest)
| _ -> None
let rec (|CharsOf|) set = function
| CharOf set (c, CharsOf set (cs, rest)) -> c::cs, rest
| rest -> [], rest
let (|StringOf|_|) set = function
| CharsOf set (_::_ as cs, rest) -> Some(System.String(Array.ofList cs), rest)
| _ -> None
type Token =
| Int of int
| Add | Sub | Mul | Div | Mod
| Unknown
let lex: string -> _ =
let digits = set ['0'..'9']
let ops = Set.ofSeq "+-*/%"
let rec lex chars =
seq { match chars with
| StringOf digits (s, rest) -> yield Int(int s); yield! lex rest
| CharOf ops (c, rest) ->
let op =
match c with
| '+' -> Add | '-' -> Sub | '*' -> Mul | '/' -> Div | '%' -> Mod
| _ -> failwith "invalid operator char"
yield op; yield! lex rest
| [] -> ()
| _ -> yield Unknown }
List.ofSeq >> lex
lex "1234 + 514 / 500"
// seq [Int 1234; Add; Int 514; Div; Int 500]
这可能很有用:看看这个解析器生成器工具可以帮助您完成的更复杂的任务:
let parse2 (text : char seq) =
let accumulate (res, acc) c =
if c = ' ' then (Seq.append res (Seq.singleton acc), "")
else (res, acc + string c)
let (acc, last) = text |> Seq.fold accumulate (Seq.empty, "")
Seq.append acc (Seq.singleton last)
parse2 "The FOX is mine"
val it : seq<string> = seq ["The"; "FOX"; "is"; "mine"]
let rec (|CharOf|_|) set = function
| c :: rest when Set.contains c set -> Some(c, rest)
| ' ' :: CharOf set (c, rest) -> Some(c, rest)
| _ -> None
let rec (|CharsOf|) set = function
| CharOf set (c, CharsOf set (cs, rest)) -> c::cs, rest
| rest -> [], rest
let (|StringOf|_|) set = function
| CharsOf set (_::_ as cs, rest) -> Some(System.String(Array.ofList cs), rest)
| _ -> None
type Token =
| Int of int
| Add | Sub | Mul | Div | Mod
| Unknown
let lex: string -> _ =
let digits = set ['0'..'9']
let ops = Set.ofSeq "+-*/%"
let rec lex chars =
seq { match chars with
| StringOf digits (s, rest) -> yield Int(int s); yield! lex rest
| CharOf ops (c, rest) ->
let op =
match c with
| '+' -> Add | '-' -> Sub | '*' -> Mul | '/' -> Div | '%' -> Mod
| _ -> failwith "invalid operator char"
yield op; yield! lex rest
| [] -> ()
| _ -> yield Unknown }
List.ofSeq >> lex
lex "1234 + 514 / 500"
// seq [Int 1234; Add; Int 514; Div; Int 500]