kog*_*oia 4 c# haskell interface typeclass
我知道这里有一个类似的问题,但我希望看到一个例子,它清楚地表明,你不能做什么,interface可以使用Type Class
为了比较,我将给你一个示例代码:
class Eq a where
(==) :: a -> a -> Bool
instance Eq Integer where
x == y = x `integerEq` y
Run Code Online (Sandbox Code Playgroud)
C#代码:
interface Eq<T> { bool Equal(T elem); }
public class Integer : Eq<int>
{
public bool Equal(int elem)
{
return _elem == elem;
}
}
Run Code Online (Sandbox Code Playgroud)
如果没有正确理解,请纠正我的例子
根据类型解析类型类,而接口调度则针对显式接收器对象进行解析.类型类参数隐式提供给函数,而C#中的对象是显式提供的.例如,您可以编写以下使用Read该类的Haskell函数:
readLine :: Read a => IO a
readLine = fmap read getLine
Run Code Online (Sandbox Code Playgroud)
您可以将其用作:
readLine :: IO Int
readLine :: IO Bool
Run Code Online (Sandbox Code Playgroud)
并拥有read编译器提供的适当实例.
您可以尝试Read使用接口模拟C#中的类
public interface Read<T>
{
T Read(string s);
}
Run Code Online (Sandbox Code Playgroud)
但是然后执行ReadLine需要一个Read<T>你想要的'实例' 的参数:
public static T ReadLine<T>(Read<T> r)
{
return r.Read(Console.ReadLine());
}
Run Code Online (Sandbox Code Playgroud)
该Eq类型类需要两个参数具有相同类型的,而你的Eq界面不会因为第一个参数是隐式接收器的类型.例如,您可以:
public class String : Eq<int>
{
public bool Equal(int e) { return false; }
}
Run Code Online (Sandbox Code Playgroud)
你不能代表使用Eq.接口隐藏了接收器的类型,因此隐藏了其中一个参数的类型,这可能会导致问题.想象一下,你有一个不可变堆数据结构的类型类和接口:
class Heap h where
merge :: Ord a => h a -> h a -> h a
public interface Heap<T>
{
Heap<T> Merge(Heap<T> other);
}
Run Code Online (Sandbox Code Playgroud)
合并两个二进制堆可以在O(n)中完成,而在O(n log n)中合并两个二项式堆是可能的,而对于fibonacci来说它是O(1).Heap接口的实现者不知道其他堆的实际类型,因此被迫使用次优算法或使用动态类型检查来发现它.相反,实现Heap类型类的类型确实知道表示.
AC#接口定义了一组必须实现的方法.Haskell类型类定义了一组必须实现的方法(可能还有一些方法的默认实现).所以那里有很多相似之处.
(我猜一个重要的区别是,在C#中,接口是一种类型,而Haskell将类型和类型类视为严格分离的东西.)
在关键的区别是,在C#中,当你定义一个类型(即,写一个类),可以定义到底是什么接口它实现,这被冻结所有的时间.在Haskell中,您可以随时向现有类型添加新接口.
例如,如果我SerializeToXml在C#中编写一个新接口,那么我就无法制作double或String实现该接口.但是在Haskell,我可以定义我的新SerializeToXml类型的类,然后让所有的标准,内置类型实现该接口(Bool,Double,Int...)
另一件事是多态在Haskell中是如何工作的.在OO语言中,您将调度对象被调用的方法的类型.在Haskell中,实现该方法的类型可以出现在类型签名中的任何位置.最重要的是,read调度你想要的返回类型 - 在OO语言中通常根本不能做的事情,甚至不能使用函数重载.
而且,在C#中,很难说"这两个参数必须具有相同的类型".然后,OO以Liskov替换委托人为基础; 两个下降的类Customer应该是可以互换的,那么为什么要将两个Customer对象约束为同一类型的客户呢?
想想看,OO语言在运行时进行方法查找,而Haskell在编译时进行方法查找.这并不是很明显,但Haskell多态实际上比通常的OO多态更像C++模板.(但这并不是特别与类型类有关,而是Haskell如何实现多态性.)
其他人已经提供了很好的答案。
我只想添加一个有关它们差异的实际示例。假设我们要建模一个“向量空间”类型类/接口,其中包含 2D、3D 等向量的常见操作。
在哈斯克尔:
class Vector a where
scale :: a -> Double -> a
add :: a -> a -> a
data Vec2D = V2 Double Double
instance Vector (Vec2D) where
scale s (V2 x y) = V2 (s*x) (s*y)
add (V2 x1 y1) (V2 x2 y2) = V2 (x1+x2) (y2+y2)
-- the same for Vec3D
Run Code Online (Sandbox Code Playgroud)
在 C# 中,我们可能会尝试以下错误的方法(我希望我的语法正确)
interface IVector {
IVector scale(double s);
IVector add(IVector v);
}
class Vec2D : IVector {
double x,y;
// constructor omitted
IVector scale(double s) {
return new Vec2D(s*x, s*y);
}
IVector add(IVector v) {
return new Vec2D(x+v.x, y+v.y);
}
}
Run Code Online (Sandbox Code Playgroud)
我们这里有两个问题。
首先,scale只返回 an IVector,它是实际Vec2D. 这很糟糕,因为缩放不会保留类型信息。
其次,add是类型错误!我们不能使用v.x因为v是一个IVector可能没有该x字段的任意值。
事实上,接口本身是错误的:该add方法承诺任何向量必须与任何其他向量相加,因此我们必须能够对 2D 和 3D 向量求和,这是无稽之谈。
通常的解决方案是切换到F 有界量化AKA CRTP 或现在被称为的任何东西:
interface IVector<T> {
T scale(double s);
T add(T v);
}
class Vec2D : IVector<Vec2D> {
double x,y;
// constructor omitted
Vec2D scale(double s) {
return new Vec2D(s*x, s*y);
}
Vec2D add(Vec2D v) {
return new Vec2D(x+v.x, y+v.y);
}
}
Run Code Online (Sandbox Code Playgroud)
程序员第一次遇到这种情况时,通常会对看似“递归”的行感到困惑Vec2D : IVector<Vec2D>。我当然是:) 然后我们习惯了这一点,并接受它作为一个惯用的解决方案。
可以说类型类在这里有一个更好的解决方案。
经过对这个问题的长期研究,我找到了一种简单的解释方法。至少对我来说很清楚。
想象一下我们有这样的签名方法
public static T[] Sort(T[] array, IComparator<T> comparator)
{
...
}
Run Code Online (Sandbox Code Playgroud)
并实施IComparator:
public class IntegerComparator : IComparator<int> { }
Run Code Online (Sandbox Code Playgroud)
然后我们可以编写这样的代码:
var sortedIntegers = Sort(integers, new IntegerComparator());
Run Code Online (Sandbox Code Playgroud)
我们可以改进这段代码,首先我们创建Dictionary<Type, IComparator>并填充它:
var comparators = new Dictionary<Type, IComparator>()
{
[typeof(int)] = new IntegerComparator(),
[typeof(string)] = new StringComparator()
}
Run Code Online (Sandbox Code Playgroud)
重新设计了 IComparator 接口,以便我们可以像上面那样编写
Run Code Online (Sandbox Code Playgroud)public interface IComparator {} public interface IComparator<T> : IComparator {}
之后让我们重新设计Sort方法签名
public class SortController
{
public T[] Sort(T[] array, [Injectable]IComparator<T> comparator = null)
{
...
}
}
Run Code Online (Sandbox Code Playgroud)
如您所知,我们将注入IComparator<T>并编写如下代码:
new SortController().Sort<int>(integers, (IComparator<int>)_somparators[typeof(int)])
Run Code Online (Sandbox Code Playgroud)
正如您已经猜到的,在我们概述实现并添加之前,此代码不适用于其他类型Dictionary<Type, IComparator>
注意,我们只会在运行时看到异常
现在想象一下,如果编译器在构建期间为我们完成了这项工作,并且如果找不到具有相应类型的比较器,则会抛出异常。
为此,我们可以帮助编译器添加一个新的关键字而不是使用属性。OutSort方法将如下所示:
public static T[] Sort(T[] array, implicit IComparator<T> comparator)
{
...
}
Run Code Online (Sandbox Code Playgroud)
以及具体Comparator的实现代码:
public class IntegerComparator : IComparator<int> implicit { }
Run Code Online (Sandbox Code Playgroud)
注意,我们使用关键字“implicit”,之后编译器将能够执行我们上面编写的例行工作,并且将在编译时抛出异常
var sortedIntegers = Sort(integers);
// this gives us compile-time error
// because we don't have implementation of IComparator<string>
var sortedStrings = Sort(strings);
Run Code Online (Sandbox Code Playgroud)
并将这种实现风格命名为Type Class
public class IntegerComparator : IComparator<int> implicit { }
Run Code Online (Sandbox Code Playgroud)
我希望我理解正确并且解释得通俗易懂。
PS:该代码不会假装工作。
| 归档时间: |
|
| 查看次数: |
693 次 |
| 最近记录: |