Haskell 类型类和 C++模板类
是否可以使用 C++(或 C#)模板来模拟 Haskell 的类型类功能?
这样做有意义吗?或者这样做有什么回报吗?
我试图用 C++ 创建一个 Functor 类,但没成功。我尝试了这样的事情:
#include <iostream>
using namespace std;
//A function class to make types more readable
template <class input, class output> class Function {
private:
output (*ptrfunc )(input);
public:
Function(output (* ptr)(input)) {
ptrfunc = ptr;
}
output call(input x) {return (*ptrfunc)(x);}
output operator() (input x) { return call(x);}
};
//the functor "typeclass"
template <class a> class Functor{
public:
template <class b> Functor<b> fmap(Function<a,b> func);
};
// an container type to be declared "instance" of functor:
template <class a> class List : public Functor<a> {
private:
a * ptrList;
int size;
public:
List(int n) { //constructor;
ptrList = new a[n];
size = n;
}
List(List<a> const& other) { //copy constructor
size = other.size;
ptrList = new a[size];
for(int i = 0; i<size; i++)
(*this)[i] = other[i];
}
~List() { delete ptrList;} //destructor
a& operator[](int i) { return ptrList[i];} // subscript operator just for easy notation
const a& operator[](int i) const { return ptrList[i];}// subscript operator just for easy notation
template <class b> List<b> fmap(Function<a,b> func) { //"instance" version of fmap
List<b> temp(size);
for(int i = 0; i < size; i++)
temp[i] = func((*this)[i]);
return temp;
}
};
int test(int k) { return 2 * k;}
int main(void) {
Function<int, int> func(&test);
List<int> lista(10);
for(int i = 0; i < 10; i++)
lista[i] = i;
List<int> lista2(lista.fmap(func));
for(int i = 0; i < 10; i++)
cout << lista2[i] << " ";
cout << endl;
return 0;
}
它做了它应该做的事情,但是在 C++ 中使用这种模式有意义吗?它真的与haskell中的模式相同吗:
data List a = -- some stuff
class Functor f where
fmap :: (a -> b) -> f a -> f b
instance (Functor List) where
-- some stuff
对我来说似乎不是同一件事,因为在Functor f
中,f
是一种* -> *
类型构造函数,并且在我上面的 Functor
定义中,a
不是模板 a
,而是“包含”数据类型本身。
有没有办法解决这个问题?更重要的是:尝试将这种模式复制到 C++ 是否有意义?在我看来,C# 比 C++ 更类似于函数式编程风格。有没有办法在 C# 中做到这一点?
Is it possible to emulate the type class functionality of Haskell with C++ (or C#) templates?
Does it make sense or is there any payoff in doing that?
I was trying to make a Functor class in C++ and I wasn't able. I tried something like this:
#include <iostream>
using namespace std;
//A function class to make types more readable
template <class input, class output> class Function {
private:
output (*ptrfunc )(input);
public:
Function(output (* ptr)(input)) {
ptrfunc = ptr;
}
output call(input x) {return (*ptrfunc)(x);}
output operator() (input x) { return call(x);}
};
//the functor "typeclass"
template <class a> class Functor{
public:
template <class b> Functor<b> fmap(Function<a,b> func);
};
// an container type to be declared "instance" of functor:
template <class a> class List : public Functor<a> {
private:
a * ptrList;
int size;
public:
List(int n) { //constructor;
ptrList = new a[n];
size = n;
}
List(List<a> const& other) { //copy constructor
size = other.size;
ptrList = new a[size];
for(int i = 0; i<size; i++)
(*this)[i] = other[i];
}
~List() { delete ptrList;} //destructor
a& operator[](int i) { return ptrList[i];} // subscript operator just for easy notation
const a& operator[](int i) const { return ptrList[i];}// subscript operator just for easy notation
template <class b> List<b> fmap(Function<a,b> func) { //"instance" version of fmap
List<b> temp(size);
for(int i = 0; i < size; i++)
temp[i] = func((*this)[i]);
return temp;
}
};
int test(int k) { return 2 * k;}
int main(void) {
Function<int, int> func(&test);
List<int> lista(10);
for(int i = 0; i < 10; i++)
lista[i] = i;
List<int> lista2(lista.fmap(func));
for(int i = 0; i < 10; i++)
cout << lista2[i] << " ";
cout << endl;
return 0;
}
It does what it's supposed to do, but does it make any sense to use this pattern in C++? Is it really the same pattern as in haskell:
data List a = -- some stuff
class Functor f where
fmap :: (a -> b) -> f a -> f b
instance (Functor List) where
-- some stuff
It doesn't seem to be the same thing to me, cause in Functor f
, f
is a kind * -> *
type constructor, and in my definition above in Functor<a>
, a
is not a template a<something>
, but the "contained" data type itself.
Is there a way out to this? And more importantly: it makes sense to try to copy this kinds of patterns to C++? It seems to me that C# is more akin to functional programming style than C++. Is there a way to do this in C#?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我对 C++ 模板不够熟悉,无法回答这个问题。不过我可以谈谈 C# 泛型类型。
简短的回答是,不。 “高级”类型类的 Haskell 系统比 C# 中的泛型类型系统更强大。
对于仍在阅读本文但不熟悉 Haskell 的读者来说,简要讨论“高级类型”的含义可能会有所帮助。在 C# 中,您可以这样做:
“通用”类型“单一类型参数的 IEnumerable”本身并不是真正的“类型”,它是一种模式,您可以通过替换类型参数(“int ") 用于类型参数 ("T")。从这个意义上说,它比普通类型“更高”。
您可以对泛型类型的类型参数施加约束:
泛型类型 C 可以使用任何类型参数构造,只要类型参数是可通过引用或装箱转换隐式转换为
IEnumerable
。但 Haskell 类型系统比这更进一步。它支持“类型类”,其中您可以对 T 施加的约束是“T 上定义了一个相等运算符”之类的内容。在 C# 中,运算符被定义为静态方法,并且没有类似于静态方法的接口。在 C# 中,我们无法基于任意静态方法泛化多种类型。
通常为 Haskell 给出的一个例子是“monad”模式。在 C# 表示法中,假设我们有一个类型:
monad 只是一种模式;一元类型是任何泛型类型,它具有静态泛型方法 Bind 和与上述模式匹配的构造函数。在 Haskell 中,你可以使用更高的类型来描述该模式;在 C# 中,我们在类型系统中没有工具来概括静态方法和构造函数等内容。
或者,您可能会说使用实例绑定器会更惯用:
这有帮助吗?不。即使把构造函数的问题放在一边,我们也无法想出一个捕获这种模式的接口。我们可以尝试:
但是这是不对的。这就是说,一个 monad 是一个接受一个 monad 和一个返回一个 monad 的函数,并返回一个 monad 的东西。这意味着您可以有两个
IMonad
实现,例如Maybe
和Sequence
,然后有一个活页夹它需要一个序列并返回一个可能!这没有任何意义;我们想要捕获的模式是,但我们无法在 C# 中表达它。
让我们考虑一下您的 Functor 示例。在 C# 中,我们可能有一个类型
,或者更惯用地讲,一个实例方法:
或者,再更惯用地讲,我们可能有静态方法作为扩展方法。 (事实上,这个方法确实存在于C#中的序列运算符库中;它可以通过在
IEnumerable
上组合“Select”和“ToList”来构建)。任何。没关系。要点是,在 Haskell 中的代码中:
您可以说“任何公开满足上述模式的映射操作的泛型类型都被称为‘函子’”,然后您可以创建采用函子的方法。我们没有任何方法可以在 C# 中概括“在用户级别提供映射操作的所有类型”。
为了解决类型系统的这种限制,我们所做的就是选择一些最强大的高级类型并将它们直接构建到语言中。语言本身可以识别更高的类型,例如序列模式(在 foreach 循环处理中)、广义 monad 模式(在查询理解中;“SelectMany”是任意 monad 上的“Bind”)、延续模式(在“await”中出现) C# 5)、“Maybe” monad(可空值类型)等等。
因此,为了解决您的特定问题,是的,没问题,进行投影的概念不是在类型系统中捕获的,而是在语言中捕获的。 /em> 带有 LINQ 查询推导式。如果您说,
那么任何类型的表达式 y 具有采用 y 和从 x 到 z 的映射的 Select 方法都可以工作。该模式已内置到 C# 语言中。但如果您想描述一些其他“更高”的模式,那么您就不走运了。
如果类型系统中有一个工具来描述更高的类型,那就太好了,但更有可能的是,我们将保持类型系统不变,并根据需要将更多模式烘焙到语言中。
我经常看到尝试在 C# 中模拟高阶类型的地方在这个问题中进行了描述:
为什么这个通用约束在看起来有循环引用时会编译
这里的想法是开发人员希望开发类型“动物”,这样:
虚构的“where T : THISTYPE”试图传达这样的想法:猫只能与另一只猫交朋友,狗只能与另一只狗交朋友,等等。 (暂时忽略这样一个事实,即这种模式意味着 MakeFriends 在形式参数类型上具有虚拟协变性,它不是类型安全的,因此可能会违反里氏替换原则。)这个概念可以用更高的类型来表达,但不能用C# 类型系统。人们有时会使用这样的模式:
然而,这实际上并不能强制执行所需的约束,因为当然没有什么可以阻止你说:
现在狗可以与猫成为朋友,这违反了动物作者的意图。 C# 类型系统的功能根本不足以表示您可能需要的所有类型的约束。您必须使用更高的类型才能使其正常工作。
I am not sufficiently versed in C++ templates to answer the question. I can talk about C# generic types a bit though.
The short answer is, no. The Haskell system of "higher" type classes is more powerful than the generic type system in C#.
A brief discussion of what we mean by "higher types" might be useful at this point for any readers that are still reading this who are not familiar with Haskell. In C# you can do this:
The "generic" type "IEnumerable of one type parameter" is not really a "type" per se, it is a pattern from which you can construct infinitely many new types, by substituting type arguments ("int") for type parameters ("T"). In that sense it is "higher" than a normal type.
You can put constraints on type parameters of generic types:
Generic type C can be constructed with any type argument, so long as the type argument is a type that is implicitly convertible via reference or boxing conversion to
IEnumerable<int>
.But the Haskell type system goes one step further than that. It supports "type classes" where the constraints you can put on the T are things like "T has an equality operator defined on it". In C#, operators are defined as static methods, and there is no analogue of an interface for static methods. We have no way in C# of generalizing across many types based on arbitrary static methods.
An example usually given for Haskell is the "monad" pattern. In C# notation, suppose we have a type:
A monad is just a pattern; a monadic type is any generic type such that it has a static generic method Bind and a constructor that matches the pattern above. In Haskell you can use higher types to describe that pattern; in C#, we have no facility in the type system for generalizing on things like static methods and constructors.
Or, you might say that it would be more idiomatic to use an instance binder:
Does that help? No. Even leaving the problem with the constructor aside, we cannot come up with an interface that captures this pattern. We can try:
But that is not right. That says that a monad is something that takes a monad and a function that returns a monad in, and returns a monad. That means that you could have two implementations of
IMonad<T>
, sayMaybe<T>
andSequence<T>
and then have a binder that takes a sequence and returns a maybe! That doesn't make any sense; the pattern we want to capture isbut we have no way of expressing that in C#.
Let's consider your Functor example. In C# we might have a type
Or, perhaps more idiomatically, an instance method:
Or, again more idiomatically, we might have the static method as an extension method. (In fact, this method does exist in the sequence operator library in C#; it can be built by composing "Select" on
IEnumerable<T>
with "ToList").Whatever. Doesn't matter. The point is that in your code in Haskell:
You can say that "any generic type which exposes a mapping operation that meets the pattern above is said to be a 'Functor'", and then you can make methods that take Functors. We don't have any way of generalizing "all the types that provide mapping operations" at the user level in C#.
To get around this limitation of the type system, what we've done is chosen a few of the most powerful higher types and built them directly into the language. The language itself recognizes higher types like the sequence pattern (in the foreach loop processing), the generalized monad pattern (in query comprehensions; "SelectMany" is "Bind" on an arbitrary monad), the continuation pattern (in "await" coming in C# 5), the "Maybe" monad (in nullable value types) and so on.
So to solve your specific problem, yes, no problem, the notion of making a projection is not captured in the type system but it is captured in the language with LINQ query comprehensions. If you say
then any expression y of a type that has a Select method that takes y and a mapping from x to z will work. That pattern has been built into the C# language. But if you want to describe some other "higher" pattern, you're out of luck.
It would be nice to have a facility in the type system to describe higher types, but what is more likely is that we will keep the type system as-is, and bake more patterns into the language as-needed.
A place where I commonly see an attempt to emulate higher-order types in C# is described in this question:
Why does this generic constraint compile when it seems to have a circular reference
The idea here is that the developer wishes to develop a type "Animal" such that:
The fictional "where T : THISTYPE" is attempting to get across the idea that a Cat can only make friends with another Cat, a Dog can only make friends with another Dog, and so on. (Ignore for the moment the fact that such a pattern, which implies that MakeFriends has virtual covariance on formal parameter types, would not be typesafe and would probably thereby violate the Liskov Substitution Principle.) That concept is expressible in higher types, but not in the C# type system. People sometimes use a pattern like:
However, this fails to actually enforce the desired constraint, because of course there is nothing whatsoever stopping you from saying:
And now a Dog can be friends with a Cat, violating the intention of the author of Animal. The C# type system is simply not powerful enough to represent all the sorts of constraints that you might want. You'd have to use higher types to make this work properly.
C++0x 原本打算引入与 Haskell 的类型类非常相似的概念,但该功能被否决了。再耐心等待十年,他们可能最终会进入这门语言。
C++0x was going to introduce concepts which are quite similar to Haskell's type classes, but the feature got voted out. Be patient for another ten years, and they may finally make their way into the language.
来自此博客的作者关于 Haskell 和 C++ 的帖子:
虽然可以在 C++ 模板中模拟许多 Haskell 功能,但翻译后的语法确实很难看。这主要是因为 C++ 中的元编程是一个意外,而不是最初设想的功能。
From the author of this blog post on Haskell and C++:
While it's possible to emulate many Haskell features in C++ templates, the translated syntax is indeed ugly. That's mostly because metaprogramming in C++ was an accident rather than an originally conceived feature.
尽管帖子中单独列出了 C# 和 C++,但与 Scala(2.8,目标是JVM)代替。 Scala 与 C# 非常相似,因为它是一种强/静态单调度“OO”语言——但它具有比 C#3/4 更强大的类型系统(但是,功能并不完全重叠)。
我不熟悉 Haskell,但多年来我读过各种文章并听到过许多论点,我相信您正在寻找的类型类功能可以通过 隐式(示例),即使不像 Haskell 那样惯用。
另一方面,F# 作为一种“函数式语言”,对类型类构造的支持很差(或者不存在?这不是我的领域,但我相信不可能在 F# 中创建 Monad 类型)。发挥语言的优势绝对是值得的。
快乐编码。
Even though C# and C++ were singled out in the post, it may be interesting to compare with Scala (2.8, target is JVM) instead. Scala is quite similar to C# as it is a strong/static single-dispatch "OO" language -- but it has a more powerful type-system than C#3/4 (the features aren't a full overlap, however).
I am not familiar with Haskell, but I have read various articles and heard a number of arguments over the years, and I believe that the typeclass functionality you are looking for can be derived with implicits (an example), even if not nearly as idiomatic as in Haskell.
On the other hand, F#, a "functional language", has poor (or non-existent? once again, not my area, but I believe it's not possible to create a Monad type in F#) support for typeclasses constructs. It definitely pays to play off the strengths of the language.
Happy coding.
也许我在这里缺少一些 Haskell 元级别,但是如果没有声明,使用 STL 的实际示例将如下所示:
Maybe I'm missing some Haskell meta level here, but your actual example, without the declarations would look like this using the STL: