问题描述
|
我是D的新手,我正在寻找一种使用类似Haskell的类型类进行编程的好方法,例如D中的函子,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)));
}
现在,您可以将int和double用作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,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个type参数。有没有一种方法您只需要指定实例,就可以从中推导出其他参数?
有什么替代笨拙的mixin解决方法的方法吗?
我没有看到这种方法的其他缺点吗?
那其他方式呢?
感谢您阅读本文并抽出时间思考和回答:)
编辑:
是否可以使用D中的unittest来定义像函子定律这样的约束?那肯定很不错。
解决方法
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);
}
这样就足够了。这样,也不需要9英镑。
是否可以定义约束
就像在单元测试中的函子定律
D吗
不能完全确定我了解您的要求,但是您可以使用模板函数/类来定义约束:
void isEven(T)(T x) if (isIntegral!T) { return x % 2 == 0; }
仅当T
是整数类型时,此模板才会实例化。
请参阅“模板”页面底部的“模板约束”部分。
, 与其回答您的问题,不如回答您的问题。我只是在讨论您正在使用的D的功能以及可能对您有用的那些功能。
D没有类型类(如您所知)。相反,它具有类型专用化(您正在使用)和模板约束。类型专门化先于模板约束,实际上可以在模板约束中使用。
模板约束允许您要求类型的某些属性。您会发现它在std.range中大量使用,并且有一些模板可以帮助在std.traits中编写此类约束。我可能会做一个更复杂的示例,但是现在,它接受转换为int的类型:
void myFunction(T)(T param) if(is(T:int)) {
}
,
是否可以使用D中的unittest来定义像函子定律这样的约束?那肯定很不错。
Phobos在语言之上还有另一个概念,就像Monoid,函子和Monad一样。那就是范围。现在,Phobos检查类型是否为范围的方法是定义一个模板,该模板检查是否可以在类型上调用某些函数。如果这些函数本身是通用的,模板的答案将取决于编译器是否能够找到与您的类型匹配的方法。
为了防卫,这是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来使用它们,就好像它们是函数本身一样。