Позднее статическое связывание в d

Я работаю над общим шаблоном класса коллекции, скажем, List(T) где я хотел бы иметь возможность делать что-то вроде поздней статической привязки php. Лучше всего это проиллюстрировать упрощенным примером кода. Этот код прекрасно компилируется, как и на dmd, но для этого нужно внести небольшие изменения.

module main;

import std.stdio;
import std.string;

class List(T)
{
    private T[] _list;

    public void append(T t)
    {
        _list ~= t;
    }

    // this is where some help is needed...
    public List select(bool delegate(T t) dg)
    {
            // auto should be whatever subclass of List(T) is calling this method.
        auto result = new List!T();
        foreach(t; _list)
        {
            if (dg(t)) result.append(t);
        }
        return result;
    }

    int opApply(int delegate(ref T) dg)
    {
        int result = 0;

        for (int i = 0; i < _list.length; i++)
        {
            result = dg(_list[i]);
            if (result)
                break;
        }
        return result;
    }
}

enum Gender
{
    MALE,
    FEMALE,
    SECRET
}

class Person
{
    private string _firstName;
    private string _lastName;
    private string _email;
    private Gender _gender;

    @property public string firstName() {return _firstName;}
    @property public string lastName() {return _lastName;}
    @property public string email() {return _email;}
    @property public Gender gender() {return _gender;}


    public this()
    {
    }

    public this(string firstName, string lastName, Gender gender = Gender.SECRET, string email = "info@example.com")
    {
        this();

        this._firstName = firstName;
        this._lastName = lastName;
        this._gender = gender;
        this._email = email;
    }

    override public string toString()
    {
        if (email.length > 0)
        {
            return "%s %s <%s>".format(firstName, lastName, email);
        }
        else
        {
            return "%s %s".format(firstName, lastName);
        }
    }
}

class PeopleList : List!Person
{
    // I would like to be able to make this: public PeopleList selectByGender(Gender gender)
    public List!Person selectByGender(Gender gender)
    {
        return select(p => p.gender == gender);
    }
}

void main(string[] args)
{
    auto people = new PeopleList();
    people.append(new Person("Kris", "Herlaar", Gender.MALE));
    people.append(new Person("John", "Doe", Gender.MALE));
    people.append(new Person("Steve", "Wozniak", Gender.MALE));
    people.append(new Person("Walter", "Bright", Gender.MALE));
    people.append(new Person("Amelia", "Earhart", Gender.FEMALE, null));
    people.append(new Person("Susan", "Anthony", Gender.FEMALE, null));

    foreach(p; people.selectByGender(Gender.FEMALE))
    {
        writeln(p);
    }
}

Как мне быть уверенным, что PeopleList.select также вернет экземпляр PeopleList вместо List!Person так что закомментированная декларация selectByGender верно?

Я мог бы, наверное, издеваться с Object.factory(this.classinfo.name) внутри реализации и на самом деле получить правильный тип экземпляра для result но я думаю, что это не поможет с объявленным типом возврата.

Я хотел бы сделать возможными методы цепочки, поэтому мне нужен компилятор, который позволил бы мне возвращать экземпляры любого вызываемого подкласса List(T).select Я полагаю, что это можно сделать с помощью вложенного шаблона, но не смог придумать ничего, что могло бы скомпилировать, не говоря уже о том, чтобы казаться элегантным.

Дополнительная информация в ответ на полученный отзыв

Я в курсе std.algorithm и из filter, В реальной жизни; этот код представляет не реальный вариант использования, а мысленный эксперимент, чтобы узнать больше о возможностях / пределах D и его шаблонов.

2 ответа

Решение

Ты можешь использовать Template This Parameters как описано в http://dlang.org/template.html

Вот пример кода со страницы.

interface Addable(T) {
    final R add(this R)(T t) {
        return cast(R)this; // cast is necessary, but safe
    }
}

class List(T) : Addable!T {
    List remove(T t) {
        return this;
    }
}

void main() {
    auto list = new List!int;
    list.add(1).remove(1); // ok
}

И вот ваш код, использующий его.

module main;

import std.stdio;
import std.string;

class List(T)
{
    private T[] _list;

    public void append(T t)
    {
        _list ~= t;
    }

    // select is now templatized based on the most derived class
    public This select(this This)(bool delegate(T t) dg)
    {
        // auto should be whatever subclass of List(T) is calling this method.
        auto result = new This();
        foreach(t; _list)
        {
            if (dg(t)) result.append(t);
        }
        return result;
    }

    int opApply(int delegate(ref T) dg)
    {
        int result = 0;

        for (int i = 0; i < _list.length; i++)
        {
            result = dg(_list[i]);
            if (result)
                break;
        }
        return result;
    }
}

enum Gender
{
    MALE,
    FEMALE,
    SECRET
}

class Person
{
    private string _firstName;
    private string _lastName;
    private string _email;
    private Gender _gender;

    @property public string firstName() {return _firstName;}
    @property public string lastName() {return _lastName;}
    @property public string email() {return _email;}
    @property public Gender gender() {return _gender;}


    public this()
    {
    }

    public this(string firstName, string lastName, Gender gender = Gender.SECRET, string email = "info@example.com")
    {
        this();

        this._firstName = firstName;
        this._lastName = lastName;
        this._gender = gender;
        this._email = email;
    }

    override public string toString()
    {
        if (email.length > 0)
        {
            return "%s %s <%s>".format(firstName, lastName, email);
        }
        else
        {
            return "%s %s".format(firstName, lastName);
        }
    }
}

class PeopleList : List!Person
{
    public PeopleList selectByGender(Gender gender)
    {
        return this.select(p => p.gender == gender);
    }
}

void main(string[] args)
{
    auto people = new PeopleList();
    people.append(new Person("Kris", "Herlaar", Gender.MALE));
    people.append(new Person("John", "Doe", Gender.MALE));
    people.append(new Person("Steve", "Wozniak", Gender.MALE));
    people.append(new Person("Walter", "Bright", Gender.MALE));
    people.append(new Person("Amelia", "Earhart", Gender.FEMALE, null));
    people.append(new Person("Susan", "Anthony", Gender.FEMALE, null));

    foreach(p; people.selectByGender(Gender.FEMALE))
    {
        writeln(p);
    }
}

Это неудачное использование наследства. PersonList не должно существовать: это никоим образом не полиморфно.

Я думаю, что вы намерены сделать, это предоставить вспомогательный метод: выбор людей по полу из списка. D имеет то, что называется унифицированным синтаксисом вызова функций, что позволяет вам вызывать свободные функции, как если бы первый параметр был действительным this пример. Таким образом, вы можете переписать свой код следующим образом:

public List!People selectByGender(List!People list, Gender gender)
{
    return list.select(p => p.gender == gender);
}

void main(string[] args)
{
    auto people = new List!People();

    // ...

    foreach(p; people.selectByGender(Gender.FEMALE))
    {
        writeln(p);
    }
}

Я не знаю, смотрели ли вы еще на std.algorithm. Но это в основном делает все ваши List(T) избыточный код Вы можете просто создать массив (или любой другой range из Person) с вашими лицами, а затем сделать:

foreach (p; people.filter!(p => p.gender == Gender.FEMALE))
{
    writeln(p);
}

и покончим с этим. Этот стиль напоминает (основные элементы) функциональное программирование, каналы и фильтры или компонентное программирование (в сообществе D), как бы вы это ни называли. Также filter et al не будет размещать новый список, а будет генерировать их результаты из исходного массива на лету, или лениво, или в потоковом режиме. Вы можете заставить их сохранить результаты в новом буфере, используя array из стандартного массива

Это один из основных случаев, когда принуждение к мысли в иерархии наследования - не самый элегантный путь.

Другие вопросы по тегам