Как использовать чистый в D 2.0

Во время игры с 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?

Ответ 1

Так как v2.050 D смягчил определение pure, чтобы принять так называемые "слабо чистые" функции. Это относится к функциям, которые " не читают и не записывают какое-либо глобальное изменяемое состояние". Слабые чистые функции не одинаковы как чистые функции в смысле функционального языка. Единственное отношение состоит в том, что они делают реальные чистые функции, а .k.a. "сильно чистые" функции способны вызывать слабые, например, пример 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;
}

Ответ 2

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

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

Во-первых, вы можете сделать это следующим образом:

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

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

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

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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