D 中的特征可以用于类型类吗?
我是 D 的新手,我正在寻找一种在 D 中使用类似 Haskell 的类型类(例如 Functor、Monoid 等)进行编程的好方法。
在 Tango 或 Phobos 中是否实现了类似的功能?
我听说过可以对某些属性进行编译时类型检查的特征。它们可以用于类型类吗?
我尝试了一些模板专业化,并提出了这个:
// Monoid.d
// generic Monoid gets called when there is no instance of Monoid for Type T
class Monoid(T) {
pragma(msg, "Type is not a Monoid");
}
// Monoid instance for double
class Monoid(T:double) {
static T mzero() { return 0; }
static T mappend(T a, T b ) { return a + b;}
}
// Monoid instance for int
class Monoid(T:int) {
static T mzero() { return 0; }
static T mappend(T a, T b ) { return a + b;}
}
类型参数需要是 Monoid 的通用算法可以表示为:
template genericfunctions() {
T TestMonoid(T,N = Monoid!T)(T a) {
return N.mappend(N.mzero(),a);
}
}
但是,如果您想省略模板参数,则必须导入所有需要的Monoid 实例并混合在 genericfunctions
模板中。
import Monoid;
import std.stdio;
import std.conv;
mixin genericfunctions;
void main() {
writefln(to!string(TestMonoid(3)));
writefln(to!string(TestMonoid(3.3243)));
}
您现在可以使用整数和双精度数作为 Monoids。
然而,当你有像 Functor 这样的类型类,其实例本身是通用的时,事情会变得更加复杂:
module Functors;
// generic Functor like generic Monoid
class Functor(alias T, A) {
pragma(msg,"Not an instance of Functor");
}
// very simple container to demonstrate functors behavior
class FunctorTest(A) {
public A a;
this(A a) {
this.a = a;
}
}
// instance of Functor for FunctorTest!A
class Functor(alias T:FunctorTest,A) {
static T!B fmap(B)(T!A a, B delegate(A) fn) {
return new T!B(fn(a.a));
}
}
一种算法看起来像这样:
template genericfunctions() {
T TestMonoid(T,N = Monoid!T)(T a) {
return N.mappend(N.mzero(),a);
}
// F is the Functor, A the functors type before,
// B the functors Type after, N is the instance of Functor
F!B fmap(alias F,A,B,N=Functor!(F,A))(F!A a, B delegate(A) fn) {
return N.fmap!B(a,fn);
}
}
幸运的是,你可以在使用它时省略四个模板参数:
mixin genericfunctions;
void main() {
auto a = new FunctorTest!int(3);
auto b = fmap(a,(int b) {return b+ 0.5;});
writefln(to!string(b.a));
}
但是当你想使用另一个 Functor 实例时Type 必须指定 fmap 的所有 4 个类型参数。有没有一种方法只需要指定实例,其他参数就可以从中推导出来?
除了笨拙的 mixin 解决方法之外,还有其他选择吗?
这种方法还有其他我没有看到的缺点吗?
那其他方法呢?
感谢您阅读本文并花时间思考和回答:)
编辑:
是否可以在 D 中使用单元测试来定义像函子定律这样的约束?那太好了。
I'm new to D, and I'm looking for a good way to program with Haskell-like type classes e.g. Functors, Monoids, etc. in D.
Is something like this implemented in Tango or Phobos?
I've heard about traits which enable compile-time type checking for certain properties. Can they be used for type classes?
I've tried a little bit with template specialization and come up with this:
// Monoid.d
// generic Monoid gets called when there is no instance of Monoid for Type T
class Monoid(T) {
pragma(msg, "Type is not a Monoid");
}
// Monoid instance for double
class Monoid(T:double) {
static T mzero() { return 0; }
static T mappend(T a, T b ) { return a + b;}
}
// Monoid instance for int
class Monoid(T:int) {
static T mzero() { return 0; }
static T mappend(T a, T b ) { return a + b;}
}
A generic algorithm whose type parameter needs to be a Monoid could then be expressed as:
template genericfunctions() {
T TestMonoid(T,N = Monoid!T)(T a) {
return N.mappend(N.mzero(),a);
}
}
However, if you want to omit the template parameters, you have to import all needed Monoid instances and mixin the genericfunctions
template.
import Monoid;
import std.stdio;
import std.conv;
mixin genericfunctions;
void main() {
writefln(to!string(TestMonoid(3)));
writefln(to!string(TestMonoid(3.3243)));
}
You can now use ints and doubles as Monoids.
However things get more complex when you have a type class like Functor whose instances are itself generic:
module Functors;
// generic Functor like generic Monoid
class Functor(alias T, A) {
pragma(msg,"Not an instance of Functor");
}
// very simple container to demonstrate functors behavior
class FunctorTest(A) {
public A a;
this(A a) {
this.a = a;
}
}
// instance of Functor for FunctorTest!A
class Functor(alias T:FunctorTest,A) {
static T!B fmap(B)(T!A a, B delegate(A) fn) {
return new T!B(fn(a.a));
}
}
One algorithm would look like this:
template genericfunctions() {
T TestMonoid(T,N = Monoid!T)(T a) {
return N.mappend(N.mzero(),a);
}
// F is the Functor, A the functors type before,
// B the functors Type after, N is the instance of Functor
F!B fmap(alias F,A,B,N=Functor!(F,A))(F!A a, B delegate(A) fn) {
return N.fmap!B(a,fn);
}
}
Luckily, you can omit the four template parameters when you use it:
mixin genericfunctions;
void main() {
auto a = new FunctorTest!int(3);
auto b = fmap(a,(int b) {return b+ 0.5;});
writefln(to!string(b.a));
}
But when you want to use another Functor instance for the Type you have to specify all 4 type parameters of fmap. Is there a way in which you only need to specify the Instance and the other parameters could be deduced from this?
Is there an alternative to the clumsy mixin workaround?
Are there other disadvantages of this approach which I don't see?
What about other ways?
Thanks for reading this far and for taking the time to think and answer :)
Edit:
Is it possible to define constraints like the functor laws with unittest in D? That would be very nice.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
而不是回答您的问题,因为这需要理解您所说的内容。我只是要漫谈一下您正在使用的 D 功能以及那些可能对您有用的功能。
D 没有类型类(如您所知)。相反,它具有类型专门化(您正在使用的)和模板约束。类型专门化先于模板约束,并且实际上可以在那里使用。
模板约束允许您要求类型的某些属性。您会发现这在 std.range 中被大量使用,并且有一些模板可以帮助在 std.traits 中编写此类约束。我可以做一个更复杂的例子,但现在,它接受转换为 int 的类型:
Rather than answer your question, as that would require understanding what you have said. I'm just going to ramble on about features of D that you are using and those that might be of use to you.
D doesn't have Type Classes (as you know). Instead it has type specialization (which you are using) and template constraints. Type specialization came prior to template constraints and can in fact be used there.
A template constraint allows you to require certain properties of a type. You will find this is heavily used in std.range and there are templates which help write such constraints in std.traits. I may do a more complicated example but for now, this accepts types which convert to int:
Phobos 在语言之上还有另一个概念,如幺半群、函子和单子。这就是范围。现在,Phobos 检查类型是否为范围的方式是定义一个模板来检查是否可以在类型上调用某些函数。如果这些函数本身是通用的,则模板的答案将取决于编译器能否找到与您的类型匹配的方法。
作为参考,这里是类型检查a ForwardRange (链接指向包含更多文档的代码):
使用它,您可以创建一个模板约束,如下所示:
注意 UFCS 与上面的 fmap 一样,fmap 仍然符合您的声明。
另请注意,使用结构而不是类可能更好。由于它们是值类型,我们可以在 D 中进行编译时函数执行 (CTFE),通过巧妙地使用 opCall,您可以像函数本身一样使用它们。
Phobos has another concept on top of the language, as monoids and functors and monads would be. And that is Ranges. Now the way Phobos is checking if a type is a range is by defining a template that checks if certain functions can be called on a type. If these functions are generic themselves, the answer of the template would depend on the compiler being able to find a method matching your type.
For refenrence, here's the typecheck for a ForwardRange (Link points to code with more docs):
With that you can create a template constraint like so:
Note the UFCS with fmap above, fmap still matches your decalaration.
Also note that it might be better to use structs instead of classes. Since they are value types and we can have Compile Time Function Execution (CTFE) in D, with clever usage of opCall you can use them as if they were functions themselves.
没必要:
那应该足够了。这样,就不再需要
mixin
了。不完全确定我理解您的要求,但您可以使用模板函数/类定义约束:
只有当
T
是整数类型时,此模板才会实例化。请参阅模板页面底部的“模板约束”部分。
No need for that:
That should suffice. With this, there's no need for the
mixin
either.Not entirely sure I understand what you are asking for, but you can define contraints with template functions/classes:
This template will only then instantiate if
T
is an integral type.See the 'Template Constraints' section at the bottom of the Templates page.