Voldemort типы в D

в 15:27, , рубрики: dlang, voldemort, переводы, Программирование, Проектирование и рефакторинг, метки: ,

Данный пост расскажет об уникальной фишке D — Voldemort типы. Типы, которые можно использовать, но нельзя назвать. Данное название не очень подходит им, но Walter Bright очень любит так их называть. Voldemort типы очень часто встречаются в стандартной библиотеке Phobos, особенно в модулях std.algorithm и std.array. Осваивающие D могут часами штудировать документацию в поисках типа, возвращаемого из splitter или joiner, а возвращают они именно Voldemort типы. После этого поста можно смело открывать исходники std.algorithm, ибо никакие Сами-Знаете-Кто вам будут не страшны.

Он самый

Иногда, взаимодействие существующих возможностей может привести к неожиданным сюрпризам. Мне нравится считать, что мы изначально заложили Voldemort типы в D, но на самом деле они были найдены Андреем Александреску. Что это за Voldermort типы? Читайте дальше.

Во-первых, немного вводной информации. Для понимания материала необходимо иметь представление о Ranges. В первом приближении, они были придуманы как замена итераторам и очень схожи с подходом C# с его интерфейсом IEnumerable. В языке D любой тип, реализующий следующие методы, будет представлять из себя InputRange:

front - get the first element of the range
popFront - remove the first element of the range
empty - are there more elements in the range?

Эти методы реализуют основу для итерации по типу. Теперь just fo fun, давайте спроектируем InputRange, который вернет бесконечную последовательность случайных чисел. (Мы также называем такие функции генераторами.)

Он может выглядеть так (не очень хороший генератор случайных чисел, но скоро им станет):

module rnd;
 
struct RandomNumberGenerator {
 
    this(uint seed) {
        next = seed;
    popFront();  // get it going
    }
 
    @property int front() {
    return ((next / 0x10000) * next) >> 16;
    }
 
    void popFront() {
    next = next * 1103515245 + 12345;
    }
 
    @property bool empty() { return false; }
 
  private:
    uint next;
  }

И функция, которая вернет его:

RandomNumberGenerator generator(uint seed) {
  return RandomNumberGenerator(seed);
}

И прекрасная программа, которая напечатает 10 таких чисел:

import std.stdio;
import rnd;
 
void main() {
  int count;
  foreach (n; generator(5)) {
      writeln(n);
      if (++count == 10)
      break;
  }
}

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

Я бы мог отметить его атрибутом private и другие модули, кроме rnd, не смогли бы получить к нему доступ. Но этот тип все еще здесь, вне зоны, которой он принадлежит, и другие члены модуля все еще могут обращаться к нему, запривачен он или нет (в D private объявления не спрятаны от других объявлений внутри одного модуля).

Перейдем теперь к более веселым вещам. Сперва, D поддерживает вывод типов для объявлений, поэтому я могу написать так:

auto g = RandomNumberGenerator(seed);

И g будет автоматически присвоен тип RandomNumberGenerator. Это стандартная вещь. Подергав эту ниточку еще немного, и мы можем выводить возвращаемые из функций типы:

auto square(double d) {
  return d * d;
}
 
auto x = square(2.3);

И компилятор поймет, что функция square вернет double, так как это тип выражения после return. И конечно, переменная x также будет типа double. Теперь давайте перепишем нашу функцию для генератора таким образом:

module rnd;
 
auto generator(uint seed) {
 
  struct RandomNumberGenerator {
 
  @property int front() {
      return ((seed / 0x10000) * seed) >> 16;
  }
 
  void popFront() {
      seed = seed * 1103515245 + 12345;
  }
 
  @property bool empty() { return false; }
  }
 
  RandomNumberGenerator g;
  g.popFront();    // get it going
  return g;
}

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

Мы можем только получить экземпляр этого типа:

auto g = generator(5);

И дальше использовать g. Я знаю о чем вы думаете — использовать typeof и создать другой экземпляр RandomNumberGenerator:

auto g = generator(4);
typeof(g) h;

Sorry, это не сработает, компилятор не позволит объявить Voldermort тип вне его области видимости (техническая причина — нет доступа к локальной переменной seed).

Теперь осталась только одна деталь, которая меня раздражает, цикл:

int count;
foreach (n; generator(5)) {
    writeln(n);
    if (++count == 10)
    break;
}

Он выглядит так старомодно. С помощью ranges, мы можем обойтись без толстого цикла, и вместо него использовать range take , чтобы просто взять первые 10 элементов этого range:

void main() {
  foreach (n; take(generator(5), 10))
  writeln(n);
}

И дальше использовать writeln, чтобы совсем избавиться от цикла:

void main() {
  writeln(take(generator(5), 10));
}
Являются ли Voldemort типы действительно только existential типами?

Дан тип T, и тип U, который может быть извлечен из T и который состоит в определенном отношении c T, является existential типом.

Для примера, если у нас есть тип T, являющийся указателем, мы можем вывести из него базовый existential тип U с помощью:

import std.stdio;
 
void extractU(T)(T t) {
  static if (is(T U : U*))
    writefln("type %s is a pointer to an %s", typeid(T), typeid(U));
  else
    writefln("type %s is not a pointer", typeid(T));
}
 
void main() {
  int* t;
  extractU(t);
  double d;
  extractU(d);
}

Что выведет на экран:

type int* is a pointer to an int
type double is not a pointer

В то время как Voldemort типы безусловно прячут свою реализацию, это, однако, не делает их existential типами. Так как Voldemort типы не могут быть получены из некого über типа, они не являются existential.

Заключение

Voldemort типы стали замечательным открытием в D, что позволило инкапсулировать типы так, что их можно использовать, но нельзя назвать.

Источник: Walter Whight Voldemort Types In D

Автор: NCrashed

Источник

Поделиться

* - обязательные к заполнению поля