这个问题导致了F#3.0中与流类型无关的parsec实现 - 受FParsec的启发,从CharStreams中解放出来并简化:http://corsis.github.com/XParsec/
在FParsec启发的流类型无关的简单parsec实现中,我想知道如何在类型级别区分以下内容:
具体来说,我如何限制F#
many1?
skipMany1?'?
只能使用类型声明为使用流的解析器吗?
F#是否为Haskell的newtype
提供了类似的构造?
是否有更多F#特定的方法来解决这个问题?
// Copyright (c) Cetin Sert 2012
// License: Simplified BSD.
#if INTERACTIVE
#else
module XParsec
#endif
open System
open System.Collections.Generic
module Streams =
type 'a ArrayEnumerator (a : 'a [], ?i : int) as e =
let l = a.Length
let mutable s = -1 |> defaultArg i
member e.Current = a.[s]
member e.Reset () = s <- -1 |> defaultArg i
member e.MoveNext () = let i = s + 1 in if i < l then s <- i; true else false
member e.MoveBack () = let i = s - 1 in if i > -1 then s <- i; true else false
member e.State with get () = s and set i = if i < l then s <- i else raise <| ArgumentOutOfRangeException()
member e.Copy () = new ArrayEnumerator<_>(a, s)
static member inline New (a : 'a []) = new ArrayEnumerator<_>(a)
interface 'a IEnumerator with
member i.Current = e.Current
interface Collections.IEnumerator with
member i.Current = e.Current :> obj
member i.MoveNext () = e.MoveNext ()
member i.Reset () = e.Reset ()
interface IDisposable with
member i.Dispose () = ()
type 'a IEnumerator with
member inline e.Copy () = (e :?> 'a ArrayEnumerator).Copy ()
member inline e.MoveBack () = (e :?> 'a ArrayEnumerator).MoveBack ()
type 'a E = 'a IEnumerator
type 'a AE = 'a ArrayEnumerator
type 'a S = 'a E
open Streams
type 'a Reply = S of 'a | F
type 'a Reply with
member inline r.Value = match r with S x -> x | F -> raise <| new InvalidOperationException()
member inline r.IsMatch = match r with F -> false | S _ -> true
static member inline FromBool b = if b then S () else F
static member inline Negate r = match r with F -> S () | S _ -> F
static member inline Map f r = match r with F -> F | S x -> S <| f x
static member inline Put x r = match r with F -> F | S _ -> S x
static member inline Choose f r = match r with F -> F | S x -> match f x with Some v -> S v | None -> F
type 'a R = 'a Reply
type Parser<'a,'b> = 'a S -> 'b R
module Primitives =
open Operators
let inline attempt (p : Parser<_,_>) (s : _ S) = s.Copy() |> p
let inline Δ<'a> = Unchecked.defaultof<'a>
let inline pzero (_ : _ S) = S Δ
let inline preturn x (_ : _ S) = S x
let inline current (e : _ S) = e.Current |> S
let inline one (e : _ S) = if e.MoveNext() then e |> current else F
let inline (?->) b x = if b then Some x else None
let inline (!!>) (p : Parser<_,_>) e = e |> p |> Reply<_>.Negate
let inline (|->) (p : Parser<_,_>) f e = e |> p |> Reply<_>.Map f
let inline (|?>) (p : Parser<_,_>) f e = e |> p |> Reply<_>.Choose f
let inline (>.) (p : Parser<_,_>) (q : Parser<_,_>) e = match p e with F -> F | S _ -> q e
let inline (.>) (p : Parser<_,_>) (q : Parser<_,_>) e = match p e with F -> F | S p -> q e |> Reply<_>.Put p
let inline (.>.) (p : Parser<_,_>) (q : Parser<_,_>) e = match p e with F -> F | S p -> q e |> Reply<_>.Map (fun q -> (p,q))
let inline (</>) (p : Parser<_,_>) (q : Parser<_,_>) e = match p e with F -> q e | s -> s
let inline private back (s : _ S) = s.MoveBack() |> ignore
let inline many (p : Parser<_,_>) (s : _ S) = let r = ref Δ in let q = Seq.toList <| seq { while (r := p s; (!r).IsMatch) do yield (!r).Value } in back s; S q
let inline many1 (p : Parser<_,_>) (s : _ S) = s |> many p |> Reply<_>.Choose (function _::_ as l -> Some l | _ -> None)
let inline array n (p : Parser<_,_>) (s : _ S) = s |> many p |> Reply<_>.Choose (function l -> let a = l |> List.toArray in (a.Length = n) ?-> a)
let inline skipMany' (p : Parser<_,_>) (s : _ S) = let c = ref 0 in (while (p s).IsMatch do c := !c + 1); back s; S !c
let inline skipMany (p : Parser<_,_>) (s : _ S) = s |> skipMany' p |> Reply<_>.Put ()
let inline skipMany1' (p : Parser<_,_>) (s : _ S) = s |> skipMany' p |> Reply<_>.Choose (fun n -> if n > 0 then Some n else None)
let inline skipMany1 (p : Parser<_,_>) (s : _ S) = s |> skipMany1' p |> Reply<_>.Put ()
let inline skipN i p s = s |> skipMany' p |> Reply<_>.Choose (fun n -> if n = i then Some () else None)
let inline (!*) p s = skipMany p s
let inline (!+) p s = skipMany1 p s
答案 0 :(得分:9)
不,F#没有像newtype
那样的东西。
如果要声明一个新类型(类型检查器将其视为不同类型),则必须将其定义为包装器,例如使用单例区分联合:
type NewParser = NP of OldParser
区分类型的多个变体的另一种方法是使用幻像类型。这是非常微妙的技术,并没有经常使用(更多的研究主题),但我写了an article about using it with F# async,它非常强大。
F#中的一般设计原则是保持简单,所以这可能太多了,但这是一个例子:(顺便说一句:我还建议使用更少的运算符和更容易理解的更多命名函数)< / p>
// Interfaces that do not implement anything, just represent different parser kinds
type ParserBehaviour =
interface end
type ConstParser =
inherit ParserBehaviour
type ForwardParser =
inherit ParserBehaviour
在解析器的定义中,您现在可以添加一个未使用的类型参数,并且必须是以下接口之一:
type Parser<'T, 'F when 'F :> ParserBehaviour> =
P of (IEnumerator<char> -> 'T)
现在,您可以使用其行为来注释解析器:
let current : Parser<_, ConstParser> = P (fun c -> c.Current)
let next : Parser<_, ForwardParser> = P (fun c -> c.MoveNext; c.Current)
如果你想编写一个只能在不改变Ienumerator
的解析器上工作的函数,你可以要求Parser<'T, ConstParser>
。对于可以处理所有这些功能的功能,您可以使用Parser<'T, 'B>
。
...但正如我所说,这是相当先进的,有些人会认为这是F#中的黑魔法。编程的F#方法与Haskell完全不同。创建简单易用的库比在每种情况下完全类型安全更重要。