使用F#中的类型和整数约束进行设计

时间:2015-04-16 04:59:56

标签: f# ocaml

如何在F#中设计一个初始化为0的整数类型,并且有一些方法总是单调地增加该值,即首先调用方法返回1,第二次调用2等。

这是OCAML代码,用于描述我希望在F#

中实现的目标
module TERM : sig
  type t
  val succ: t -> t
  val init: unit -> t
  val compare: t -> t -> int
end

module Term : TERM = struct
  type t = int
  with compare
  let succ = succ
  let init() = 0
end

4 个答案:

答案 0 :(得分:4)

你可以将它直接翻译成一个类型(也应该在ML中也可以):

type Term =
   | Init
   | Succ of Term

当然它可能不是最高效的,所以你可能会喜欢这样的东西(更接近你的版本):

module Term =

   type T = private T of int

   let init ()             = T 0
   let succ (T i)          = T (i+1)
   let compare (T a) (T b) = compare a b      

遗憾的是,F#没有Ocaml的强大模块,所以我在这里修复了int

通用版

如果你真的想要,你可以使它更通用:

module Term =

    type T<'a> = 
        private T of 'a with
        override x.ToString () = 
            match x with 
            | (T i) -> i.ToString()


    let inline init ()      = T LanguagePrimitives.GenericZero
    let inline succ (T i)   = T (i + LanguagePrimitives.GenericOne)
    let compare (T a) (T b) = compare a b
    let getValue (T i)      = i

但我不知道你之后是否会喜欢这些用法/类型:

> Term.init () |> Term.succ;;
val it : Term.T<int> = 1

> let (t : Term.T<float>) = Term.init () |> Term.succ;;
val t : Term.T<float> = 1

比较和东西

为了清楚起见:我添加了compare只是为了在行中添加了问题。 您并不真正需要,因为type T默认会支持此功能。

例如:

module Term =

    type T<'a> = private T of 'a 

    let inline init ()      = T LanguagePrimitives.GenericZero
    let inline succ (T i)   = T (i + LanguagePrimitives.GenericOne)

将按预期工作:

> let one = Term.init () |> Term.succ;;
val one : Term.T<int>

> let two = Term.succ one;;
val two : Term.T<int>

> one = Term.succ (Term.init ());;
val it : bool = true

> one < two;;
val it : bool = true

> one >= two;;
val it : bool = false

> compare one two;;
val it : int = -1

如果你关心struct / value类型

与@Vandroiys一起回答或进行微调:

module Term =

    type [<Struct>] T<'a> internal (a : 'a) = 
        member internal __.value = a

    let inline init ()         = T (LanguagePrimitives.GenericZero)
    let inline succ (i : T<_>) = T (i.value + LanguagePrimitives.GenericOne)

答案 1 :(得分:3)

最小值类型实现:

type [<Struct>] Term private (i : int) =
    member __.Value = i
    member __.Next = Term (i + 1)
    // optional static members
    static member Zero = Term ()
    static member value (t : Term) = t.Value
    static member next  (t : Term) = t.Next
  • 默认支持平等和比较。你不需要额外的代码。
  • 结构Term()所需的默认构造函数将创建一个零值结构,值为0
  • 这是一种价值类型;它的使用不会导致额外的堆分配。

  • 静态成员Zero是某些库方法中使用的标准名称&#39;成员约束,例如LanguagePrimitives.GenericZero。这种兼容性在这里并不重要,但它是一个合适的标准名称;就个人而言,我使用Zero或默认构造函数代替init

使用测试:

Term.Zero.Next = (Term.Zero |> Term.next) // true
Term.Zero < Term.Zero.Next                // true

这种方法的推广不允许 Next 实例成员(静态成员很好),因为类型不支持F#的成员约束。如果这是一个要求,我还会考虑一个 internal 构造函数,或Carsten König's answer的方法,它使用单例DU来允许模块私有构造。


如果有人来这里只是寻找一个返回增加整数的函数,Str在问题的评论中暗示的是:

/// Returns 1 on first call, otherwise last issued + 1
let intSource =
    let i = ref 0
    fun () -> incr i; i

答案 2 :(得分:2)

不幸的是,F#并不完全支持ML风格的模块(或仿函数)。现有的答案涵盖了使用惯用F#来处理可以使用现有模块但受到相当限制的某些情况的方法。

一个更忠实但更少惯用且更麻烦的翻译可能看起来更像这样:

type TermRec<'t> = {
    succ: 't -> 't
    init: unit -> 't
    compare: 't -> 't -> int
}

type TermUser<'z> =
    abstract Use : TermRec<'t> -> 'z

type Term =
    abstract Apply : TermUser<'z> -> 'z

let mkTerm r = { new Term with member this.Apply(u) = u.Use r }

let term1 = mkTerm { succ = (fun i -> i + 1); init = (fun() -> 0); compare = compare }

let term2 = mkTerm { succ = (fun i -> not i); init = (fun() -> false); compare = compare }

let user = { 
    new TermUser<_> with 
        member this.Use(t) = 
            t.compare (t.init() |> t.succ |> t.succ) (t.init()) }

let v1 = term1.Apply user
let v2 = term2.Apply user

特别要注意:

  1. 完全支持具有不同类型参数的术语(int s,bool等。)。
  2. 条款隐藏其基础TermRec<_>的类型参数,并且会泄漏此类型值的用户将无法编译(尽管您可以创建使用的TermUser。 NET反射在运行时打破封装并返回例如类型的字符串名称。

答案 3 :(得分:0)

如果我理解正确并且您希望创建一个每次调用时都会提供新值的函数,通常的解决方案是使用ref作为计数器并将其保密。在OCaml中,代码是:

let create_counter initial_value =
  let counter = ref initial_value in
  fun () ->
    let result = !counter in
    incr counter;
    result

使用如下:

# let new_id = create_counter 0;;
val new_id : unit -> int = <fun>
# new_id ();;
- : int = 0
# new_id ();;
- : int = 1
# new_id ();;
- : int = 2
# new_id ();;
- : int = 3