F#转换文本的惯用方式

F#转换文本的惯用方式,f#,tail-recursion,F#,Tail Recursion,迈洛!因此,我正在寻找一种简洁、高效、惯用的F#解析文件或字符串的方法。我强烈倾向于将输入视为字符序列(char seq)。其思想是,每个函数负责解析一段输入,返回与未使用的输入组成元组的转换文本,并由更高级别的函数调用,该函数将未使用的输入链接到以下函数,并使用结果构建复合类型。因此,每个解析函数都应该有一个类似于此函数的签名:char seq->char seq*'a。例如,如果函数的职责只是提取第一个单词,那么一种方法是: let parseFirstWord (text: char se

迈洛!因此,我正在寻找一种简洁、高效、惯用的F#解析文件或字符串的方法。我强烈倾向于将输入视为字符序列(char seq)。其思想是,每个函数负责解析一段输入,返回与未使用的输入组成元组的转换文本,并由更高级别的函数调用,该函数将未使用的输入链接到以下函数,并使用结果构建复合类型。因此,每个解析函数都应该有一个类似于此函数的签名:char seq->char seq*'a。例如,如果函数的职责只是提取第一个单词,那么一种方法是:

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]