Как использовать чистую в D 2.0 - PullRequest
10 голосов
/ 17 июня 2009

Во время игры с D 2.0 я обнаружил следующую проблему:

Пример 1:

pure string[] run1()
{
   string[] msg;
   msg ~= "Test";
   msg ~= "this.";
   return msg;
}

Это компилируется и работает как положено.

Когда я пытаюсь обернуть массив строк в классе, я не могу заставить это работать:

class TestPure
{
    string[] msg;
    void addMsg( string s )
    {
       msg ~= s;
    }
};

pure TestPure run2()
{
   TestPure t = new TestPure();
   t.addMsg("Test");
   t.addMsg("this.");
   return t;
}

Этот код не будет компилироваться, потому что функция addMsg нечиста. Я не могу сделать эту функцию чистой, поскольку она изменяет объект TestPure. Я что-то пропустил? Или это ограничение?

Компилируется следующее:

pure TestPure run3()
{
    TestPure t = new TestPure();
    t.msg ~= "Test";
    t.msg ~= "this.";
    return t;
}

Не будет ли оператор ~ = реализован как нечистая функция массива msg? Почему же компилятор не жалуется на это в функции run1?

Ответы [ 5 ]

6 голосов
/ 08 марта 2011

Начиная с версии 2.050, D смягчил определение pure, чтобы принять так называемые "слабо чистые" функции. Это относится к функциям, которые « не читают и не записывают глобальное изменяемое состояние ». Слабо чистые функции не совпадают с чистыми функциями в смысле функционального языка. Единственное отношение состоит в том, что они создают действительно чистые функции, то есть «сильно чистые» функции, способные вызывать слабые, как в примере с OP.

При этом addMsg можно пометить как (слабо) pure, поскольку изменяется только локальная переменная this.msg:

class TestPure
{
    string[] msg;
    pure void addMsg( string s )
    {
       msg ~= s;
    }
};

и, конечно, теперь вы можете использовать (сильно) pure функцию run2 без изменений.

pure TestPure run2()
{
   TestPure t = new TestPure();
   t.addMsg("Test");
   t.addMsg("this.");
   return t;
}
4 голосов
/ 18 июня 2009

Другие уже указывали, что addMsg не является чистым и не может быть чистым, поскольку он изменяет состояние объекта.

Единственный способ сделать его чистым - это инкапсулировать изменения, которые вы делаете. Самый простой способ сделать это с помощью обратной мутации, и для этого есть два способа.

Во-первых, вы можете сделать это так:

class TestPure
{
    string[] msg;
    pure TestPure addMsg(string s)
    {
        auto r = new TestPure;
        r.msg = this.msg.dup;
        r.msg ~= s;
        return r;
    }
}

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

pure TestPure run3()
{
    auto t = new TestPure;
    t = t.addMsg("Test");
    t = t.addMsg("this.");
    return t;
}

Таким образом, мутация ограничивается каждой чистой функцией с изменениями, передаваемыми через возвращаемые значения.

Альтернативный способ написания TestPure состоял бы в том, чтобы сделать члены const и сделать всю мутацию перед передачей ее конструктору:

class TestPure
{
    const(string[]) msg;
    this()
    {
        msg = null;
    }
    this(const(string[]) msg)
    {
        this.msg = msg;
    }
    pure TestPure addMsg(string s)
    {
        return new TestPure(this.msg ~ s);
    }
}

Надеюсь, это поможет.

3 голосов
/ 17 июня 2009

Пожалуйста, просмотрите определение чистых функций:

Чистые функции - это функции, которые выдают одинаковый результат для одинаковых аргументов. Для этого чисто функция:

  • имеет параметры, которые являются инвариантными или неявно конвертируемыми в инвариант
  • не читает и не записывает какие-либо глобальные изменяемые состояния

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

0 голосов
/ 18 июня 2009

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

Рассмотрим случай, когда источник класса недоступен. В этом случае компилятор не сможет сказать, что addMsg только изменяет переменную-член, поэтому он не может позволить вам вызывать ее из чистой функции.

Чтобы разрешить это в вашем случае, он должен иметь особую обработку для этого типа использования. Каждое добавленное правило особого случая делает язык более сложным (или, если оставить его недокументированным, делает его менее переносимым)

0 голосов
/ 17 июня 2009

Просто догадка, но эта функция не всегда возвращает один и тот же результат.

Видите, он возвращает ссылку на некоторый объект, и хотя объект всегда будет содержать одни и те же данные, объекты, возвращаемые несколькими вызовами одних и тех же функций, не идентичны; то есть они не имеют один и тот же адрес памяти.

Когда вы возвращаете ссылку на объект, вы по существу возвращаете адрес памяти, который будет отличаться при нескольких вызовах.

Другой способ думать об этом, часть возвращаемого значения - это адрес памяти объекта, который зависит от некоторых глобальных состояний, и если вывод функции зависит от глобального состояния, то это не чисто , Черт, это даже не должно зависеть от этого; пока функция читает глобальное состояние, она не является чистой. Вызывая «new», вы читаете глобальное состояние.

...