【问题标题】:Can traits in D be used for type classes?D 中的特征可以用于类型类吗?
【发布时间】:2011-06-13 09:02:42
【问题描述】:

我是 D 新手,我正在寻找一种使用类似 Haskell 的类型类进行编程的好方法,例如D 中的 Functors、Monoids 等。

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

您现在可以将整数和双精度数用作 Monoid。

但是,当您拥有像 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 个类型参数。有没有办法只需要指定Instance,其他参数就可以推导出来了?

是否有替代笨拙的 mixin 解决方法的方法?

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

其他方式呢?

感谢您阅读本文并花时间思考和回答:)


编辑:

是否可以在 D 中使用 unittest 定义函子定律等约束?那就太好了。

【问题讨论】:

  • 不那么笨拙的方法可能是放弃在非 FP 语言中模仿纯 FP 的构造。根据您尝试做的事情,它可能(或可能不会)更容易。
  • D 是一种多范式语言,对 FP 有很好的支持,例如目前我知道的仅在 Haskell 中才有的一流函数和 PURE 函数。类型类是一个非常强大的功能,因为它们可以进行抽象编程。它们相对于继承的优势在于您可以将属性添加到现有类型。如果您有一个接口 Monoid,您将无法将 int 用作 monoid 或之前构造的其他类型。而且我认为可以将它们放在 D 中。这里很难在函数端实现,但在调用方你不认识它。

标签: functional-programming d typeclass


【解决方案1】:
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 也不需要了。

是否可以定义约束 就像带有 unittest 的函子定律一样 D?

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

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

只有当T 是一个整数类型时,这个模板才会实例化。

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

【讨论】:

  • 我认为这也足够了。它在您自己键入 N 时有效,但如果您不这样做,编译器将找不到正确的模板,因为它仅在源文件中查找该方法已定义。但是感谢您的限制提示!我去看看
  • @KIMA:你使用的是什么版本的 DMD?它对我来说很好。
【解决方案2】:

而不是回答您的问题,因为这需要理解您所说的内容。我只是想谈谈您正在使用的 D 的功能以及可能对您有用的功能。

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

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

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

【讨论】:

    【解决方案3】:

    是否可以在 D 中使用 unittest 定义函子定律等约束?那就太好了。

    Phobos 在语言之上还有另一个概念,就像类半群、函子和单子一样。那是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 仍然与您的声明匹配。

    还要注意,使用结构而不是类可能会更好。由于它们是值类型,我们可以在 D 中使用编译时函数执行 (CTFE),巧妙地使用 opCall,您可以像使用函数一样使用它们。

    【讨论】:

      猜你喜欢
      • 2019-01-05
      • 1970-01-01
      • 2013-11-21
      • 2022-08-08
      • 2013-06-28
      • 2014-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多