Вопрос

Играясь с 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?

Это было полезно?

Решение

Начиная с версии 2.050, D смягчил определение pure , чтобы принять так называемый " слабо чистый " функции тоже. Это относится к функциям, которые не читают и не записывают какие-либо глобальные изменяемые состояния & Quot ;. Слабо чистые функции - это не то же самое , что и чистые функции в смысле функционального языка. Единственное отношение состоит в том, что они делают реально чистые функции, например, "сильно чистыми". функции могут вызывать слабые, как в примере с 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;
}

Другие советы

Другие уже отмечали, что 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);
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top