F#有类似于Haskell的“newtype”吗?

6

新库: XParsec

这个问题导致了在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

Haskell的newtype只是data的优化特殊情况。因此,可能最好使用F#的type - Cat Plus Plus
1个回答

11

不,F#没有类似newtype的东西。

如果您想声明一个新类型(被类型检查器视为不同类型),则必须将其定义为包装器,例如使用单例鉴别联合:

type NewParser = NP of OldParser

另一种区分类型的多个版本的方法是使用幻影类型。这是一种相当微妙的技术,不太常用(更多是研究课题),但我写了一篇关于在 F# async 中如何使用它的文章,它非常强大。

F# 的一般设计原则是保持简单,因此这可能有点过于复杂,但这里有一个例子:(顺便说一下:我建议使用较少的运算符和更多易于理解的命名函数)

// 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 等编程语言有很大不同。创建简单易用的库比在每种情况下都完全类型安全更为重要。


4
比起在所有情况下都完全保持类型安全,创建简单易用的库更加重要。 - Don Stewart
1
@DonStewart 哇直接来自 Don Stewart :) 我一向更喜欢简单而不是类型安全,你的明智话语一定会让我感到完全自在,不会被过度的类型安全所诱惑。很高兴能同时听到你和 Tomas 的回答 :)! - Cetin Sert
4
不一定构成或意味着认可。 - Don Stewart
@DonStewart "" <- 引号!哇,我刚看到它们 :) 没有 newtype,我也不会使用虚类型。我确信 F# 4.0 或以后版本会采纳更多 Haskell 的思路,但在那之前,我更青睐于简单性+性能,而非安全性。我可以用“每个人都知道 while true do(),几乎没有人会使用它”这句话来为我的选择辩解。 - Cetin Sert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接