关于haskell:F#中是否存在诸如”刚性类型”之类的东西?

Is there such thing like “rigid type” in F#?

在Haskell类型类中可以引入"接口",其中有一些"自由"类型参数,例如"对于所有a:实例都支持此功能集"。而且,如果实例化此类类型类,则不能缩小此a类型,否则会遇到关于刚性类型a的错误(例如"对于所有对象,但现在变得更狭窄,这打破了原来的类型对所有对象的Promise")")。 OK,我在我的F#类型(类)中实现了IEnumerable接口:

1
2
    interface System.Collections.Generic.IEnumerable<byte> with
        member __.GetEnumerator () = (__.GetBytes ()).GetEnumerator ()

这正在编译,按我的预期工作,一切都很好。但是在这里,我做了一个更狭窄的接口:IEnumerable<byte>而不是IEnumerable<'Any>。那么,IEnumerable中的类型参数是什么?您可以实现更具体,更狭窄的接口,对吗?这与Haskell中的" forall"不一样,还是??


您的代码段类似于Haskell实例,而不是Haskell类。该代码段说明,周围的类型是IEnumerable<byte>的实例,类似于Haskell中的instance IEnumerable T Byte(其中T是实现IEnumerable的类型)。

我想这里的关键见解是必须将IEnumerable视为多参数类型类,其中一个参数表示序列的类型,另一个参数表示序列的元素。

在F#中,第一个参数是隐式的-接口声明中没有直接提及它,但是每个接口都有它-这是实现接口的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// F#
type MyType = MyType with
    interface IEnumerable<byte> with
        member this.GetEnumerator() = xyz


-- Haskell
class IEnumerable t e where
    getEnumerator :: t -> [e]

data MyType = MyType

instance IEnumerable MyType Byte where
    getEnumerator t = xyz

在这里,很容易看到Te实际上都是"刚性"类型。多参数性反映了这样一个事实,即同一类型T对于不同的e可能具有多个IEnumerable实现,以相同的方式,您可以在F#中的一个类型上实现多个具体的IEnumerable<e>

F#不能做的一件事(就是这样)是为任何e提供实现,例如:

1
2
instance IEnumerable MyType e where
    getEnumerator t = ...

要在F#中实现类似(但不完全相同)的效果,可以创建另一个接口并实现它:

1
2
3
4
5
6
7
8
9
10
11
type AnyEnumerable =
    abstract member AnyEnumerate<'a>() : IEnumerable<'a>

type MyType = MyType with
    interface AnyEnumerable with
        member this.AnyEnumerate<'a>() =
            { new IEnumerable<'a> with
                  member this.GetEnumerator() = ... }

let x = MyType.AnyEnumerate<int>()
let y = MyType.AnyEnumerate<string>()


在您的示例中,外部GetEnumerator()实现调用__.GetBytes(),我希望它具有类型unit -> IEnumerable<byte>,并且还将泛型类型参数也固定为byte。如果您可以说您的类型实现了IEnumerable<'Any>,那么如果我想让'Any成为bool,但是它仍然给我提供了一个字节集合,那么程序的行为应该是什么?诸如此类的程序不会在F#中进行类型检查,因为语言规范希望程序员明确进行所有此类转换,以确保对他们的程序有意义。

如果您想指定一个开放的通用接口实现,那肯定是可能的(并且很常见)。您可以让类型实现类似IEnumerable<'Any>的通用接口,但是必须在类型级别上指定类型参数'Any。所以你不能写这个

1
2
type MyEnumerable () =
    interface IEnumerable<'Any> with ...

,但是如果您在接口本身之外在类型本身上声明type参数,也可以执行以下操作:

1
2
type MyEnumerable<'Any> () =
    interface IEnumerable<'Any> with ...

这主要是由于F#起源于.NET,其中接口在泛型之前存在,并且与运行时如何使用类型紧密联系在一起,但是在其接口实现中将类型与类型参数断开连接通常总是没有任何意义的接口可能只具有非常有限的功能,或者会执行与类型本身无关的操作,无论如何这很可能是一个糟糕的设计。

现在,如果我回到您的示例,您可以尝试将'Any类型参数添加到接口及其实现它的类型,但是由于第一段中的原因,编译器会给您错误。因此,您必须确定类型本身是否真的是通用类型,在这种情况下,应该更改GetEnumerator实现,还是真的只实现"可枚举字节",在这种情况下,您的原始代码是正确的,不能再更多了广义的。