F#中的惯用方法是建立对类型而非实例级别的接口的遵从性

时间:2014-07-27 03:56:15

标签: f#

F#中最常用的处理方式是什么?假设我有一个属性,我想要一个类型来满足在实例级别上没有意义,但理想情况下我希望有一些模式匹配可用吗?

为了使这更具体,我已经定义了一个表示环概念的界面(在抽象代数意义上)。我会选择:

1

// Misses a few required operations for now
type IRing1<'a when 'a: equality> =
    abstract member Zero: 'a with get
    abstract member Addition: ('a*'a -> 'a) with get

让我们假设我这样使用它:

type Integer =
    | Int of System.Numerics.BigInteger
    static member Zero with get() = Int 0I
    static member (+) (Int a, Int b) = Int (a+b)

    static member AsRing
        with get() =
            { new IRing1<_> with
                member __.Zero = Integer.Zero
                member __.Addition = Integer.(+) }

允许我写下这样的内容:

let ring = Integer.AsRing

然后让我很好地使用我为验证环的属性而编写的单元测试。但是,我无法对此进行模式匹配。

2

type IRing2<'a when 'a: equality> =
    abstract member Zero: 'a with get
    abstract member Addition: ('a*'a -> 'a) with get

type Integer =
    | Int of System.Numerics.BigInteger
    static member Zero with get() = Int 0I
    static member (+) (Int a, Int b) = Int (a+b)

    interface IRing2<Integer> with
        member __.Zero = Integer.Zero
        member __.Addition with get() = Integer.(+)

现在我可以模式匹配,但这也意味着我可以编写废话,例如

let ring = (Int 3) :> IRing2<_>

3

我可以使用额外的间接级别并基本上定义

type IConvertibleToRing<'a when 'a: equality>
    abstract member UnderlyingTypeAsRing : IRing3<'a> with get

然后基本上构建IRing3&lt; _&gt;与#1下的方式相同。 这会让我写一下:

let ring = ((Int 3) :> IConvertibleToRing).UnderlyingTypeAsRing

这是冗长的,但至少我写的东西不再是废话。然而,除了详细程度之外,所获得的额外复杂程度并不是真正的“感觉”和#34;这里有道理。

4

我还没有完全想到这个,但我可以只有一个Integer类型而不实现任何接口,然后是一个名为Integer的模块,它允许Ring接口的绑定值。我想我可以在辅助函数中使用反射,该函数为任何类型创建任何IRing实现,其中还有一个具有相同名称的模块(但其中有一个模块后缀的编译名称)可用?这将结合#1和#2的好处我猜,但我不确定它是否可能和/或过于做作?

仅仅是为了背景:只是为了它,我试图在F#中实现我自己的迷你计算机代数系统(例如Mathematica或Maple),我想我会遇到足够的代数结构来启动引入诸如IRing之类的接口进行单元测试以及(可能)稍后处理这些代数结构的一般操作。

我意识到这里有什么或不可能的部分更多地与限制如何在.NET而不是F#中完成任务有关。如果我的意图足够明确,我很乐意在评论中评论其他函数式语言如何解决这类设计问题。

1 个答案:

答案 0 :(得分:1)

关于如何在其他函数式语言中实现Rings的问题,在Haskell中,您通常会定义一个包含所有Ring操作的Type Class Ring

在F#中没有类型类,但是你可以使用内联和重载更接近:

module Ring =
    type Zero = Zero with
        static member ($) (Zero, a:int) = 0
        static member ($) (Zero, a:bigint) = 0I
        // more overloads

    type Add = Add with
        static member ($) (Add, a:int   ) = fun (b:int   ) -> a + b
        static member ($) (Add, a:bigint) = fun (b:bigint) -> a + b
        // more overloads

    type Multiply = Multiply with
        static member ($) (Multiply, a:int   ) = fun (b:int   ) -> a * b
        static member ($) (Multiply, a:bigint) = fun (b:bigint) -> a * b
        // more overloads

    let inline zero() :'t = Zero $ Unchecked.defaultof<'t>
    let inline (<+>) (a:'t) (b:'t) :'t= (Add $ a) b 
    let inline (<*>) (a:'t) (b:'t) :'t= (Multiply $ a) b 

// Usage

open Ring

let z : int = zero()
let z': bigint = zero()

let s = 1 <+> 2
let s' = 1I <+> 2I

let m = 2 <*> 3
let m' = 2I <*> 3I

type MyCustomNumber = CN of int with
    static member ($) (Ring.Zero, a:MyCustomNumber) = CN 0
    static member ($) (Ring.Add, (CN a)) = fun (CN b) -> CN (a + b)
    static member ($) (Ring.Multiply, (CN a)) = fun (CN b) -> CN (a * b)

let z'' : MyCustomNumber = zero()

let s'' = CN 1 <+> CN 2

如果您希望使用此方法进行扩展,可以查看已使用Zero(Mempty)和Add(Mappend)定义FsControlMonoid。您可以提交Ring的拉取请求。

现在,如果您计划仅将所有这些与数字一起使用,为什么不在F#中使用GenericNumbers,(+)(*)已经是通用的,那么您有LanguagePrimitives.GenericZero和{{ 1}}。