F#中的惯用方式来建立对类型而非实例级别的接口的依从性 [英] Idiomatic way in F# to establish adherence to an interface on type rather than instance level

查看:55
本文介绍了F#中的惯用方式来建立对类型而非实例级别的接口的依从性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

F#中处理以下问题的最惯用方式是什么.假设我有一个我要满足的属性,而该类型在实例级别上是没有意义的,但是理想情况下,我想针对它使用一些模式匹配?

What's the most idiomatic way in F# to deal with the following. Suppose I have a property I want a type to satisfy that doesn't make sense on an instance level, but ideally I would like to have some pattern matching available against it?

为了更具体一点,我定义了一个表示环的概念的接口(抽象代数意义上).我会去吗?

To make this more concrete, I have defined an interface representing the concept of a ring (in the abstract algebra sense). Would I go for:

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

,让我们假设我正在像这样使用它:

and let's assume I'm using it like this:

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.(+) }

这使我可以编写以下内容:

which allows me to write things like:

let ring = Integer.AsRing

然后,我可以很好地使用我编写的用于验证环属性的单元测试.但是,我无法对此进行图案匹配.

which then lets me to nicely use the unit tests I've written for verifying the properties of a ring. However, I can't pattern match on this.

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.(+)

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

which now I can pattern match, but it also means that I can write nonsense such as

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

3.

我可以使用附加级别的间接寻址并基本上定义

I could use an additional level of indirection and basically define

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

,然后基本上按照与#1下相同的方式构造IRing3< __.这可以让我这样写:

and then basically construct the IRing3<_> in the same way as under #1. This would let me write:

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

这很冗长,但至少我正在写的内容不再是胡说八道.但是,除了冗长之外,这里获得的附加级别的复杂性实际上并没有感觉"到正当理由.

which is verbose but at least what I'm writing doesn't read as nonsense anymore. However, next to the verbosity, the additional level of complexity gained doesn't really "feel" justifiable here.

4.

我还没有完全考虑到这一点,但是我可以拥有一个Integer类型而不实现任何接口,然后创建一个名为Integer的模块,让Ring接口具有绑定值.我想我可以在辅助函数中使用反射,该函数可以为任何类型的任何IRing实现创建一个可用的同名模块(但其编译后的名称带有模块后缀)的任何类型的IRing实现?我猜这将结合#1和#2的好处,但是我不确定这是否可能和/或太人为了?

I haven't fully thought this one through yet, but I could just have an Integer type without implementing any interfaces and then a module named Integer, having let bound values for the Ring interfaces. I suppose I could then use reflection in a helper function that creates any IRing implementation for any type where there is also a module with the same name (but with a module suffix in it's compiled name) available? This would combine the benefits of #1 and #2 I guess, but I'm not sure whether it's possible and/or too contrived?

仅出于背景考虑:就此而言,我正在尝试在F#中实现自己的微型计算机代数系统(例如Mathematica或Maple),并且我发现自己会遇到足够多的代数结构以开始引入接口例如用于单元测试的IRing,以及(可能)稍后用于处理此类代数结构的一般运算.

Just for background: Just for the heck of it, I'm trying to implement my own mini Computer Algebra System (like e.g. Mathematica or Maple) in F# and I figured that I would come across enough algebraic structures to start introducing interfaces such as IRing for unit testing as well as (potentially) later for dealing with general operations on such algebraic structures.

我意识到这里可能发生的事或不可能发生的事更多地与.NET(而非F#)中如何完成事情的限制有关.如果我的意图很明确,我会在这里很好地评论其他功能语言如何解决此类设计问题.

I realize part of what is or isn't possible here has more to do with restrictions on how things can be done in .NET rather than F#. If my intention is clear enough, I'd be curious to here in comments how other functional languages work around this kind of design questions.

推荐答案

关于您如何以其他功能语言实现Rings的问题,在Haskell中,您通常会定义

Regarding your question about how can you implement Rings in other functional languages, in Haskell you will typically define a Type Class Ring with all Ring operations.

在F#中,没有类型类,但是您可以使用内联和重载来实现更紧密的联系:

In F# there are no Type Classes, however you can get closer using inline and overloading:

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

如果您想使用这种方法扩大规模,可以查看 FsControl ,该类已经定义了 Monoid ,其中零(无用)和加(Mappend).您可以提交对Ring的拉动请求.

If you want to scale up with this approach you can have a look at FsControl which already defines Monoid with Zero (Mempty) and Add (Mappend). You can submit a pull request for Ring.

如果您打算仅将所有这些都与数字一起使用,那么现在变得实用,为什么不在F#中使用GenericNumbers,(+)(*)已经是通用的,那么您有 LanguagePrimitives.GenericZero LanguagePrimitives.GenericOne .

Now to be practical if you are planning to use all this only with numbers why not use GenericNumbers in F#, (+) and (*) are already generic then you have LanguagePrimitives.GenericZero and LanguagePrimitives.GenericOne.

这篇关于F#中的惯用方式来建立对类型而非实例级别的接口的依从性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆