F#有Haskell的“新类型”吗? [英] Does F# have 'newtype' of Haskell?
问题描述
新图书馆: XParsec
这个问题已经导致F#3.0中一个与流类型无关的parsec实现 - 受FParsec启发,从CharStreams中解放出来并简化为: http://corsis.github.com/XParsec/
在FParsec-inspired stream-类型无关的简单parsec实现,我想知道如何在类型级别区分以下内容: 我在F#中进行了限制
many1?
skipMany1?'?
仅适用于type - 声明消费流?
F#是否提供类似的构造Haskell的 newtype
?
是否有解决此问题的更具体的F#方法?
代码
// Copyright(c)Cetin Sert 2012
//许可证:简化的BSD。
#if INTERACTIVE
#else
模块XParsec
#endif
打开系统
打开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<然后s <-i; true else false
member e.MoveBack()= let i = s - 1 in if i> -1,则s <-i; true else false
member e.State with get()= s并设置i = if i<然后s< - 我也提高< | ArgumentOutOfRangeException()
成员e.Copy()= new ArrayEnumerator< _>(a,s)
静态成员inline New(a:'a [])= new ArrayEnumerator< _>(a)
接口'具有
成员的IEnumerator i.Current = e.Current
接口Collections.IEnumerator具有
成员i.Current = e.Current:> obj
成员i.MoveNext()= e.MoveNext()
成员i.Reset()= e.Reset()
接口IDisposable with
成员i.Dispose() =()
类型'带有
的IEnumerator内联e.Copy()=(e:?>'a ArrayEnumerator).Copy()
内联e。 MoveBack()=(e:?>'a ArrayEnumerator).MoveBack()
type'a E ='a IEnumerator
type'a AE ='a ArrayEnumerator
输入'a S ='a E
打开数据流
输入'a回复= S of'a | F
类型'a回复
内联r.Value =匹配r与S x - > x | F - >提高< |新的InvalidOperationException()
内联成员r.IsMatch =匹配r与F - > false | S _ - >真
静态成员inline FromBool b =如果b然后S()否F
静态成员inline否定r =与F匹配r - > S()| S _ - > F
静态成员inline Map f r =与F匹配r - > F | S x - > S <| f x
静态成员内嵌Put x r =与F匹配r - > F | S _ - > S x
static member inline选择f r =与F匹配r - > F | S x - >匹配f x与某些v - > S v |无 - > F
type'a R ='a Reply
type Parser<'a,'b> ='a S - > 'b R
模块原语=
open操作符
让内联尝试(p:Parser< _,_&)(s:_S )= s.Copy()|> p
让内联Δ<'a> = Unchecked.defaultof<'a>
let inline pzero(_:_ S)= SΔ
let inline preturn x(_:_ S)= S x
让内联电流(e:_ S) = e.Current |> S
让内联一个(e:_ S)= if e.MoveNext()then e |>当前else F
let inline(? - >)bx = if b then Some x else None
let inline(!!>)(p:Parser< _,_>) e = e |> p |>回复< _> .Negate
let inline(| - >)(p:Parser< _,_>)f e = e |> p |>回复< _> .Map f
let inline(|?>)(p:Parser< _,_>)f e = e |> p |> (p:Parser< _,_>)(q:Parser< _,_>)e =使用F来匹配p - >选择f
let inline F | S _ - > (p:Parser< _,_>)(q:Parser< _,_>)e =匹配p e与F - > F | S p - > q e |> (p:Parser< _,_>)(q:Parser< _,_>)e =匹配p与F - > F | S p - > q e |> (p:Parser< _,_>)(q:Parser< _,_>)(< )e =匹配pe与F - > q e | s - > (s:_ S)= s.MoveBack()|> s
让内联私人后退忽略
让let in =许多(p:Parser< _,_>)(s:_ S)= let r = refΔin q = Seq.toList< | seq {while(r:= p s;(!r).IsMatch)在return s中做yield(!r).Value}; S q
let inline many1(p:Parser< _,_>)(s:_ S)= s |>许多p |> (p:Parser< _,_>)(s:_ S)回复< _> .Choose(函数_ :: _ as l - > Some l | _ - > None)
let inline array n = s |>许多p |> (a.Length = n)→ - > a)
let inline skipMany'(p)中的函数l - > let a = l |> List.toArray :(s(s)_s)= let c = ref 0 in(while(ps).IsMatch do c:=!c + 1);回来; S!c
let inline skipMany(p:Parser< _,_>)(s:_ S)= s |> skipMany'p |>回复< _> .Put()
let inline skipMany1'(p:Parser< _,_>)(s:_S)= s |> skipMany'p |>回复< _> .Choose(fun n - >如果n> 0 then Some n else None)
let inline skipMany1(p:Parser< _,_>)(s:_ S)= s |> ; skipMany1'p |>回复< _> .Put()
let inline skipN i p s = s |> skipMany'p |>回答< _> .Choose(fun n - >如果n = i那么Some()else None)
let inline(!*)ps = skipMany ps
let inline(!+ )ps = skipMany1 ps
不,F#没有任何类似 newtype
。
如果你想声明一个新类型被类型检查器视为不同类型),那么你必须将它定义为包装器,例如使用单例歧视联合:
type NewParser = OldParser的NP
区分一个类型的多个varsions是使用幻像类型。这是非常微妙的技巧,不常用(更多的研究主题),但我写了一篇关于使用它与F#异步,它非常强大。
F#中的一般设计原则是保持简单,所以这可能太多了,但这里是一个例子:(顺便说一句:我也建议使用更少的运算符和更易于理解的命名函数)
$ b
//接口不执行任何操作,代表不同的解析器种类
类型ParserBehaviour =
接口结束
类型ConstParser =
继承ParserBehaviour
类型ForwardParser =
继承ParserBehaviour
在解析器的定义中,您现在可以添加一个未使用的类型参数,并且必须是其中一个接口:
type Parser<'T,'F when'F:> ParserBehaviour> =
P(IEnumerator< char>'>')
现在,你可以使用它们的行为来注释解析器:
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完全不同。创建简单易用的库比在每种情况下都完全类型安全更重要。
New Library: XParsec
This question has lead to a stream-type-independent parsec implementation in F# 3.0 - inspired by FParsec, freed from CharStreams and simplified: http://corsis.github.com/XParsec/
In an FParsec-inspired stream-type-independent simple parsec implementation, I wonder how I could distinguish the following at the type level:
- parsers that consume a piece of stream
- parsers that work on the current position without moving ahead in the stream
Specifically, how can I restrict in F#
many1?
skipMany1?'?
to work only with parsers that are type-declared to consume streams?
Does F# offer a similar construct to Haskell's newtype
?
Is there a more F#-specific way to solve this problem?
Code
// 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
No, F# does not have anything like newtype
.
If you want to declare a new type (that is treated as a different type by the type checker), then you have to define it as a wrapper, for example using single-case discriminated union:
type NewParser = NP of OldParser
Another way to distinguish between multiple varsions of a type is to use phantom types. This is pretty subtle technique and is not used too often (more of a research topic), but I wrote an article about using it with F# async and it's quite powerful.
The general design principle in F# is to keep things simple, so this may be too much, but here is an example: (BTW: I'd also suggest using fewer operators and more named functions that are easier to understand)
// Interfaces that do not implement anything, just represent different parser kinds
type ParserBehaviour =
interface end
type ConstParser =
inherit ParserBehaviour
type ForwardParser =
inherit ParserBehaviour
In the definition of the parser, you can now add a type parameter that is not used and has to be one of these interfaces:
type Parser<'T, 'F when 'F :> ParserBehaviour> =
P of (IEnumerator<char> -> 'T)
Now, you can annotate parsers with their behaviour:
let current : Parser<_, ConstParser> = P (fun c -> c.Current)
let next : Parser<_, ForwardParser> = P (fun c -> c.MoveNext; c.Current)
And if you want to write a function that can only work on parsers that do not change the Ienumerator
, you can require Parser<'T, ConstParser>
. For functions that can work on all of them, you can take Parser<'T, 'B>
.
... but as I said, this is fairly advanced and some would consider this a black magic in F#. The F# approach to programming is quite different than, say, Haskell. It is more important to create simple and easy to use library than to be fully type-safe in every case.
这篇关于F#有Haskell的“新类型”吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!