Можно ли использовать черты в D для классов типов? - PullRequest
18 голосов
/ 13 июня 2011

Я новичок в D, и я ищу хороший способ программирования с классами типа, подобными Haskell, например, Functors, Monoids и т. Д. В D.

Что-то вроде этого реализовано в Tangoили Фобос?

Я слышал о чертах, которые позволяют проверять типы во время компиляции для определенных свойств.Могут ли они использоваться для классов типов?

Я немного попробовал специализацию шаблонов и придумал следующее:

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

Один алгоритм будет выглядеть следующим образом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);
    }
}

К счастью, вы можете опустить четыре параметра шаблона при его использовании:

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 для типа, вы должны указатьвсе 4 типа параметров fmap.Есть ли способ, которым вам нужно только указать Instance, и из этого можно вывести другие параметры?

Есть ли альтернатива неуклюжему обходному решению mixin?

Существуют ли другие недостаткиэтот подход, который я не вижу?

А как насчет других способов?

Спасибо, что прочитали это далеко и нашли время подумать и ответить:)


Редактировать:

Можно ли определить такие ограничения, как законы функторов с unittest в D?Это было бы очень хорошо.

Ответы [ 3 ]

4 голосов
/ 16 июня 2011
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 является целочисленным типом.

См. Раздел «Ограничения шаблонов» внизу страницы Шаблоны .

4 голосов
/ 14 июня 2011

Вместо того, чтобы ответить на ваш вопрос, так как это потребует понимания того, что вы сказали. Я просто расскажу о функциях D, которые вы используете, и о тех, которые могут быть вам полезны.

D не имеет классов типов (как вы знаете). Вместо этого он имеет специализацию типа (которую вы используете) и шаблонные ограничения . Специализация типа предшествовала шаблонным ограничениям и фактически может использоваться там.

Ограничение шаблона позволяет вам требовать определенных свойств типа. Вы найдете, что это интенсивно используется в std.range, и есть шаблоны, которые помогают записывать такие ограничения в std.traits. Я могу сделать более сложный пример, но сейчас он принимает типы, которые преобразуются в int:

void myFunction(T)(T param) if(is(T:int)) {
}
0 голосов
/ 06 сентября 2014

Можно ли определить такие ограничения, как законы функторов с unittest в D?Это было бы очень хорошо.

У Фобоса есть иная концепция поверх языка, как моноиды, функторы и монады.И это Диапазоны .Теперь способ, которым Фобос проверяет, является ли тип диапазоном, заключается в определении шаблона, который проверяет, могут ли определенные функции вызываться для типа.Если сами эти функции являются общими, то ответ шаблона будет зависеть от того, сможет ли компилятор найти метод, соответствующий вашему типу.

Для ссылки вот проверка типов для 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.
        }
}

Обратите внимание на UFCS с fmap выше,fmap по-прежнему соответствует вашей декалярации.

Также обратите внимание, что может быть лучше использовать структуры вместо классов.Так как они являются типами значений, и мы можем иметь выполнение функций времени компиляции (CTFE) в D, при умном использовании opCall вы можете использовать их так, как если бы они были самими функциями.

...