Late static binding in d

泪湿孤枕 提交于 2019-12-12 13:50:05

问题


I'm working on a generic collection class template, let's say List(T) where i'd like to be able to do something like php's late static binding. Might be best illustrated with some simplified sample code. This code compiles fine as is on dmd, but it needs a small change to be the way I want.

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

How would I go about making sure that PeopleList.select would also return an instance of PeopleList instead of a List!Person so that the commented out declaration of selectByGender is correct?

I could probably mock about with Object.factory(this.classinfo.name) inside the implementation and actually get the correct type of instance for result but I figure that would not help with the declared return-type.

I'd want to make chaining methods possible so I'd need the compiler to allow me to return instances of whatever subclass is calling List(T).select I imagine it could be done with a nested template, but haven't been able to come up with anything that would compile, let alone seem elegant.

Additional info in reply to received feedback

I am aware of std.algorithm and of filter, In real life; this code does not represent an actual use-case but a thought experiment to learn more of the abilities/limits of D and its' templates.


回答1:


You can use Template This Parameters as described in http://dlang.org/template.html#TemplateThisParameter

Here's some example code from the page.

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
}

And here's your code using it.

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



回答2:


This is an unfortunate use of inheritance. PersonList shouldn't exist: It is not in any way polymorhpic.

What I think you intend to do is to provide a helper method: selecting people by gender from a list. D has something called unified function call syntax, which allows you to call free functions as if the first parameter was the actual this instance. So you could rewrite your code like 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);
    }
}

I don't know if you've looked into std.algorithm yet. But it basically renders all your List(T) code redundant. You could just create an array (or any other range of Person) with your persons and then do:

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

and be done with it. This style resembles (essential elements of) functional programming, pipes and filters or component programming (within the D community), whatever you like to call it. Also filter et al won't allocate a new List, but rather generate their results from the original array on the fly, or lazily, or streaming. You can force them to save the results in a new buffer by using array from std.array.

This is one of the basic cases where forcing yourself to think in Inheritance hierachies is not the most elegant way to go.



来源:https://stackoverflow.com/questions/18197531/late-static-binding-in-d

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!