使用C#接口实现Haskell类型类


13

我正在尝试比较Haskell的类型类和C#的接口。假设有一个Functor

Haskell:

class Functor f where
  fmap :: (a -> b) -> f a -> f b

如何在C#中将此类型类实现为接口?

我尝试过的

interface Functor<A, B>
{
    F<B> fmap(Func<A, B> f, F<A> x);
}

这是无效的实现,实际上我受困于F应该由返回的泛型类型fmap。应该如何定义它以及在哪里?

Functor用C#不可能实现吗?为什么?也许还有另一种方法?


8
埃里克·利珀特(Eric Lippert)谈到了C#的类型系统如何真正不足以支持Haskell在此答案中定义的Functors的更高种类的性质:stackoverflow.com/a/4412319/303940
KChaloux 2013年

1
这是大约3年前。有什么变化吗?
ДМИТРИЙМАЛИКОВ

4
什么都没有改变,以使在C#这一点,我也不认为它可能在未来
JK。

Answers:


8

C#的类型系统缺少正确实现类型类作为接口所必需的几个功能。

让我们从您的示例开始,但是关键是显示一个类型类的含义和作用的完整说明,然后尝试将它们映射到C#位。

class Functor f where
  fmap :: (a -> b) -> f a -> f b

这是类型类定义,或类似于接口。现在,让我们看一下类型的定义及其类型类的实现。

data Awesome a = Awesome a a

instance Functor Awesome where
  fmap f (Awesome a1 a2) = Awesome (f a1) (f a2)

现在我们可以很明显地看到类型接口无法提供的一个明显的事实。类型类的实现不属于类型定义。在C#中,要实现接口,必须将其实现为实现该接口的类型的定义的一部分。这意味着您可能不会为自己没有实现的类型实现接口,但是在Haskell中,您可以为您有权访问的任何类型实现类型类。

那可能是立即最大的,但是还有另一个相当大的区别,那就是等效的C#实际上几乎不能很好地工作,并且您在提出问题时就谈到了它。这与多态有关。另外,Haskell还允许您使用一些相对通用的类型来处理类型类,而这些类不会完全转换,尤其是当您开始查看存在性类型或其他GHC扩展(如通用ADT)中的通用性时。

您会发现,使用Haskell可以定义函子

data List a = List a (List a) | Terminal
data Tree a = Tree val (Tree a) (Tree a) | Terminal

instance Functor List where
  fmap :: (a -> b) -> List a -> List b
  fmap f (List a Terminal) = List (f a) Terminal
  fmap f (List a rest) = List (f a) (fmap f rest)

instance Functor Tree where
  fmap :: (a -> b) -> Tree a -> Tree b
  fmap f (Tree val Terminal Terminal) = Tree (f val) Terminal Terminal
  fmap f (Tree val Terminal right) = Tree (f val) Terminal (fmap f right)
  fmap f (Tree val left Terminal) = Tree (f val) (fmap f left) Terminal
  fmap f (Tree val left right) = Tree (f val) (fmap f left) (fmap f right)

然后在消耗中您可以具有一个功能:

mapsSomething :: Functor f, Show a => f a -> f String
mapsSomething rar = fmap show rar

问题就在这里。在C#中,您如何编写此函数?

public Tree<a> : Functor<a>
{
    public a Val { get; set; }
    public Tree<a> Left { get; set; }
    public Tree<a> Right { get; set; }

    public Functor<b> fmap<b>(Func<a,b> f)
    {
        return new Tree<b>
        {
            Val = f(val),
            Left = Left.fmap(f);
            Right = Right.fmap(f);
        };
    }
}
public string Show<a>(Showwable<a> ror)
{
    return ror.Show();
}

public Functor<String> mapsSomething<a,b>(Functor<a> rar) where a : Showwable<b>
{
    return rar.fmap(Show<b>);
}

因此,有几件事情错的C#版本,有一件事我甚至不能肯定它可以让你使用<b>像我一样有资格,但没有它,我一定不会派遣Show<>适当地(随意尝试并编译以找出答案;我没有)。

但是,这里更大的问题是,与上面在Haskell中不同的是,我们将Terminals定义为类型的一部分,然后代替类型使用,这是由于C#缺乏适当的参数多态性(一旦尝试进行互操作,这一点就变得非常明显) F#和C#),您无法清楚或清楚地区分Right或Left是Terminals。最好的办法就是使用use null,但是如果您尝试将值类型设为a,Functor或者Either要区分两种都带有值的类型该怎么办?现在,您必须使用一种类型,并使用两个不同的值来检查并在之间建立模型以进行区分?

缺少适当的总和类型,联合类型,ADT,无论您想称呼它们多少,实际上都会使很多类型类不起作用,因为归根结底,它们让您将多个类型(构造函数)视为一个类型, .NET的基础类型系统根本没有这样的概念。


2
我不太精通Haskell(仅是Standard ML),所以我不知道这有什么不同,但是可以用C#编码求和类型
2014年

5

您需要两类,一类用于建模高阶泛型(函子),一类用于对具有自由值A的组合函子进行建模

interface F<Functor> {
   IF<Functor, A> pure<A>(A a);
}

interface IF<Functor, A> where Functor : F<Functor> {
   IF<Functor, B> pure<B>(B b);
   IF<Functor, B> map<B>(Func<A, B> f);
}

因此,如果我们使用Option单子(因为所有单子都是函子)

class Option : F<Option> {
   IF<Option, A> pure<A>(A a) { return new Some<A>(a) };
}

class OptionF<A> : IF<Option, A> {
   IF<Option, B> pure<B>(B b) {
      return new Some<B>(b);
   }

   IF<Option, B> map<B>(Func<A, B> f) {
       var some = this as Some<A>;
       if (some != null) {
          return new Some<B>(f(some.value));
       } else {
          return new None<B>();
       }
   } 
}

然后,您可以在需要时使用静态扩展方法将IF <Option,B>转换为Some <A>


pure在通用函子界面中遇到了困难:编译器抱怨IF<Functor, A> pure<A>(A a);“该类型Functor不能用作Functor通用方法类型中的类型参数IF<Functor, A>。没有装箱转换或从类型转换FunctorF<Functor>。” 这是什么意思?为什么我们必须pure在两个地方进行定义?此外,不应该pure是静态的吗?
Niriel

1
你好 我想是因为我在设计课程时暗示了Monad和Monad变压器。像OptionT monad转换器(在Haskell中为MaybeT)一样的monad转换器在C#中定义为OptionT <M,A>,其中M是另一个通用monad。OptionT monad转换器框位于M <Option <A >>类型的monad中,但是由于C#没有更高种类的类型,因此您需要一种在调用OptionT.map和OptionT.bind时实例化更高种类的M monad的方法。静态方法都不奏效,因为你不能叫M.pure(A一)任何单子M.
DetriusXii
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.