Подтвердить что ты не робот

Могут ли черты в D использоваться для классов типов?

Я новичок в 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;}
}

Общий алгоритм, тип параметра которого должен быть моноидом, может быть выражен следующим образом:

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

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

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

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

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


Edit:

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

4b9b3361

Ответ 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.

Можно ли определить ограничения как законы функтора с унитатом в D?

Не совсем уверен, что я понимаю, о чем вы просите, но вы можете определить contraints с функциями/классами шаблона:

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

Этот шаблон будет только тогда создавать экземпляр, если T является интегральным типом.

См. раздел "Ограничения шаблона" в нижней части страницы Шаблоны.

Ответ 2

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

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

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

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

Ответ 3

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

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

Для refenrence здесь typecheck для 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 вы можете использовать их, как если бы они были самими функциями.