D中的特征可以用于类型类吗?

时间:2011-06-13 09:02:43

标签: functional-programming d typeclass

我是D的新手,我正在寻找一种使用类似Haskell的类型编程的好方法,例如D中的函数,幺半群等。

在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))); 
}

您现在可以使用整数和双精度。

但是当你有一个像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));
}

但是当你想为Type使用另一个Functor实例时,你必须指定fmap的所有4个类型参数。有没有办法只需要指定实例,其他参数可以从中推导出来?

是否有替代笨拙的mixin变通方法?

这种方法还有其他缺点我没看到吗?

其他方法呢?

感谢你阅读这篇文章并花时间思考和回答:)


修改

是否可以在D中定义像单元测试的仿函数法这样的约束?那将是非常好的。

3 个答案:

答案 0 :(得分:4)

而不是回答你的问题,因为这需要理解你所说的话。我只是在讨论你正在使用的D的功能以及可能对你有用的功能。

D没有Type Classes(如您所知)。相反,它具有类型特化(您正在使用)和template constraints。类型专门化在模板约束之前出现,实际上可以在那里使用。

模板约束允许您需要某种类型的某些属性。你会发现它在std.range中被大量使用,并且有一些模板可以帮助在std.traits中编写这样的约束。我可能会做一个更复杂的例子但是现在,它接受转换为int的类型:

void myFunction(T)(T param) if(is(T:int)) {
}

答案 1 :(得分:4)

template genericfunctions() {
  T TestMonoid(T,N = Monoid!T)(T a) {
    return N.mappend(N.mzero(),a);
  }
}

不需要:

T TestMonoid(T,N = Monoid!T)(T a) {
  return N.mappend(N.mzero(),a);
}

这应该足够了。有了这个,就不需要mixin

  

是否可以定义约束   喜欢有单位测试的仿函数法则   d?

我不完全确定我理解你的要求,但你可以用模板函数/类来定义约束:

void isEven(T)(T x) if (isIntegral!T) { return x % 2 == 0; }

如果T是整数类型,此模板将仅实例化。

请参阅Templates页面底部的“模板约束”部分。

答案 2 :(得分:0)

  

是否可以在D中定义像单元测试的仿函数法这样的约束?那将是非常好的。

Phobos在语言之上有另一个概念,就像monoids,functor和monads一样。那是Ranges。现在,Phobos检查类型是否为范围的方式是通过定义一个模板来检查是否可以在类型上调用某些函数。如果这些函数本身是通用的,那么模板的答案将取决于编译器能够找到与您的类型匹配的方法。

对于引用,here's the typecheck for a ForwardRange(链接指向包含更多文档的代码):

template isInputRange(R)
{
    enum bool isInputRange = is(typeof(
    (inout int = 0)
    {
        R r = R.init;     // can define a range object
        if (r.empty) {}   // can test for empty
        r.popFront();     // can invoke popFront()
        auto h = r.front; // can get the front of the range
    }));
}

使用它可以创建一个模板约束,如下所示:

template isFunctor(Testant) {
    enum bool isFunctor = is(typeof(
        ()
        {
            Testant t = Testant.init;          // can instantiate that type
            auto result = t.fmap((Testant){}); // can call fmap on it with the type as parameter.
        }
}

请注意上面带有fmap的UFCS,fmap仍然与您的decalaration相匹配。

另请注意,使用结构而不是类可能更好。由于它们是值类型,我们可以在D中使用编译时功能执行(CTFE),通过巧妙地使用opCall,您可以像使用它们本身一样使用它们。