вторник, 24 марта 2009 г.

Type inference как средство криптографии :)

Сейчас в mainstream языки программирования добавляют возможность вывода типов (type inference). Т.е., если раньше в C++ нужно было писать так:

my_class * o = new my_class(...);

то теперь можно будет указывать имя типа переменной всего лишь один раз:

auto o = new my_class(...);

Возможность вывода типов, конечно, штука замечательная. Более того, в некоторых случаях просто совершенно необходимая (например, в C#3.0 вывод типов в сочетании с анонимными типами сделали возможным существование LINQ). Но, на мой взгляд, повсеместное использование вывода типов сделает программы более сложными в понимании.

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

А вот совсем свежий пример на эту тему. z00n поделился ссылочкой на небольшое сравнение производительности разных языков программирования на задаче MINILIGHT: a minimal global illumination renderer. Реализация тестовой программы на языке Scala имеет более чем вдвое меньший объем, чем C++ вариант. Но является ли Scala-вариант более читабельным? Вот фрагмент, на котором я сразу же споткнулся:

   def this( eyePosition:Vector3f, items:Array[Triangle] ) = this(
      {
         // make overall bound
         val bound =
         {
            // accommodate all items, and eye position (makes tracing algorithm
            // simpler)
            val rectBound = items.foldLeft( (eyePosition, eyePosition) )(
               (rb, item) =>
               {
                  // accommodate item
                  val ib = item.bound
                  ( (rb._1 clampedMax ib._1), (rb._2 clampedMin ib._2) )
               } )
            // make cubical
            val cube = Vector3f( (rectBound._2 - rectBound._1).fold(Math.max) )
            ( rectBound._1, (rectBound._2 clampedMin (rectBound._1 + cube)) )
         }

         // convert to array
         Array.range(0,6).map( i => (if(i < 3) bound._1 else bound._2)(i % 3) )

      // delegate to main (recursive) constructor
      }, items, 0 )

Представте себе, что у вас под рукой нет IDE, а только листинг программы (как раз то, что мы имеем, заглянув на страницу minilight). Какие типы имеют bound, rectBound, ib? Заметили ли вы, что данный конструктор на самом деле является вызовом еще одного конструктора, а все эти вычисления – всего лишь блок кода, который вычисляет значение первого параметра для второго конструктора?

Или вот еще интересный вопрос по данному фрагменту: здесь item.bound и последующее выражение в скобках – это два разный действия или же вызов функции bound:

                  // accommodate item
                  val ib = item.bound
                  ( (rb._1 clampedMax ib._1), (rb._2 clampedMin ib._2) )

Наверное, это все-таки два разных действия. Но понять это можно только присмотревшись внимательно, и увидев во второй строке использование ib внутри выражения.

В общем, с некоторым напряжением жду перехода на C++0x, в котором вывод типов уже будет реализован. Боюсь, вывод типов будет еще одной штукой в C++, требующей крайне осторожного применения.

В заключение. Самый толковый подход к декларации типов я пока видел в Eiffel. Там устранена главная проблема – дублирование имени типа при инициализации переменных. Т.е. если в C++ (Java) мы вынуждены были писать:

my_class * o = new my_class(...);

то в Eiffel это записывалось бы как:

local
  o: MY_CLASS
do
  o.make (...)

Т.е. есть явное указание типа переменной, но нет дублирования имени типа при ее инициализации. Не в последнюю очередь поэтому программы на Eiffel-е были самыми читабельными из тех, которые мне приходилось видеть (но и одними из самых объемных по количеству строк кода).

6 комментариев:

Andrey комментирует...

"в C#3.0 вывод типов в сочетании с анонимными типами сделали возможным существование LINQ" это неправда. Linq прекрасно существует без анонимных типов.

eao197 комментирует...

Не буду ввязываться в спор, т.к. по поводу .NET-а у меня знания весьма поверхностные. Но в свою защиту приведу цитату из Wikipedia (http://en.wikipedia.org/wiki/LINQ#Language_Extensions):

Anonymous types: Anonymous types allow classes, which contain only data member declarations, to be inferred by the compiler. This is useful for the Select and Join operators, whose result types may differ from the types of the original objects. The compiler uses type inference to determine the fields contained in the classes and generates accessors and mutators for these fields.

И пример:

int someValue = 5;

var results = from c in someCollection
let x = someValue * 2
where c.SomeProperty < x
select new {c.SomeProperty, c.OtherProperty};

foreach (var result in results)
{
Console.WriteLine(result);
}

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

Andrey комментирует...

Мне кажется что есть большое заблуждение про важность анонимных типов. Некоторых людей отталкивает то что они слышат, что Linq и анонимные типы без друг друга никуда.
Пример лукавый на самом деле. Ведь никто не мешает написать

class SomeEntity
{
public int SomeProperty;
public string OtherProperty;
}

var results = from c in someCollection
let x = someValue * 2
where c.SomeProperty < x
select new SomeEntity() {SomeProperty = c.SomeProperty, OtherProperty = c.OtherProperty};

foreach (var result in results)
{
Console.WriteLine(result);
}

и все. и никаких анонимных типов. причем лично я бы предпочел именно такую запись потому что она более формальная и строгая.

я довольно много пишу на Linq и никогда вообще не использую анонимные типы, и никому не советую.

eao197 комментирует...

Сейчас вообще очень сильно влияние лозунга "code less!". Видимо, разработчики C# с этим считаются. Ведь их задача -- сделать язык максимально привлекательным для самых разных людей. Вы можете писать так, как вам нравится, с большей долей формализма. Кто-то, напротив, будет рад использовать анонимные типы.

Andrey комментирует...

кто-то рад и переменные коротко (и непонятно) называть :) нельзя же винить язык за то, что он такое позволяет. код нужно нормальный писать, это должно касаться всех аспектов. я бы таже такой warning сделал, "Не используйте анонимные типы!", а в своих проектах еще бы делал его как warning as error.

eao197 комментирует...

Ну так я и не виню язык :)
Просто что-то часто на глаза попадаются программы на языках с type inference (D, Scala, C#), в которых приходится слишком долго разбираться. Именно из-за постоянного использования type inference.