вторник, 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-е были самыми читабельными из тех, которые мне приходилось видеть (но и одними из самых объемных по количеству строк кода).

Отправить комментарий