А ваш язык программирования это может? (перевод)

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

// простейший пример
alert("Я люблю спагетти!");
alert("Я люблю шоколад!");

Эти примеры написаны на JavaScript, но даже если вы его не знаете, то все равно поймете общий смысл.

Повторяющиеся куски кода выглядели неправильно, так что вы создали функцию

function SwedishChef( food )
{
    alert("Я люблю " + food + "!");
}
	
SwedishChef("спагетти");
SwedishChef("шоколад");

(Swedish Chef)

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

Теперь вы заметили еще два участка кода, которые выглядели практически одинаковыми, за исключением того, что один из них вызывал функцию PutInPan, а другой PutInPot. Больше ничего не отличалось.

alert("get the lobster");
PutInPot("lobster");
PutInPot("water");

alert("get the chicken");
PutInPan("chicken");
PutInPan("coconut");

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

function Cook( i1, i2, f )
{
    alert("get the " + i1);
    f(i1);
    f(i2);
}

Cook( "lobster", "water", PutInPot );
Cook( "chicken", "coconut", PutInPan );

Смотрите! Мы передали функцию как аргумент.

А ваш язык это может?

Подождите... предположим вы еще не определили функции PutInPot или PutInPan. Разве не было бы удобно написать их прямо здесь, вместо обьявления где-то еще?

Cook("lobster", 
     "water", 
     function(x) { alert("pot " + x); }  );
Cook("chicken", 
     "coconut", 
     function(x) { alert("pan " + x); } );

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

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

var a = [1,2,3];
	
for (i=0; i<a.length; i++)
{
    a[i] = a[i] * 2;
}
	
for (i=0; i<a.length; i++)
{
    alert(a[i]);
}

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

function map(fn, a)
{
    for (i = 0; i < a.length; i++)
    {
        a[i] = fn(a[i]);
    }
}

Теперь вы сможете переписать вышеприведенный код как:

map( function(x){return x*2;}, a );
map( alert, a );

Еще одна повседневная задача - это обьединение каким-либо способом всех значений массива

function sum(a)
{
    var s = 0;
    for (i = 0; i < a.length; i++)
    s += a[i];
    return s;
}
    
function join(a)
{
    var s = "";
    for (i = 0; i < a.length; i++)
    s += a[i];
    return s;
}
    
alert(sum([1,2,3]));
alert(join(["a","b","c"]));

sum и join выглядят настолько похожими, что возможно вам захочется абстрагировать их суть в обобщенную функцию, обьединяющую все элементы массива в одно значение:

function reduce(fn, a, init)
{
    var s = init;
    for (i = 0; i < a.length; i++)
    s = fn( s, a[i] );
    return s;
}
    
function sum(a)
{
    return reduce( function(a, b){ return a + b; }, 
    a, 0 );
}
    
function join(a)
{
    return reduce( function(a, b){ return a + b; }, 
    a, "" );
}

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

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

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

Что же, вернемся к функции map. Суть в том, что если вам надо сделать что-то с каждым элементом массива, то скорее всего нет никакой разницы, в каком порядке вы их будете перебирать. Начнете вы с конца или с начала, результат будет одним и тем же, правильно? И если у вас есть два процессора, то возможно вы напишете немного кода, чтобы каждый процессор обрабатывал половину элементов, и внезапно map начнет работать вдвое быстрее.

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

После этого написание, например, по-настоящему быстрого кода для поиска по содержимому всего интернета, сведется к вызову map с аргументом в виде простой строки.

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

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

Теперь вы понимаете, почему я был недоволен тем, что студентов-программистов не учат ничему кроме Java.

Без понимания функционального программирования вы не сможете изобрести MapReduce, алгоритм, который позволяет Google так хорошо масштабироваться. Понятия Map и Reduce пришли из Lisp и функционального программирования. С другой стороны MapReduce очевиден для всех, кто с университетских времен помнит, что чисто функциональные программы не имеют побочных эффектов и очень просто параллелизируются. Тот факт, что MapReduce изобрел Google, а не Microsoft, многое говорит о причинах того, что Microsoft все еще не довел до ума базовую функциональность поиска, а Google прешел к следующему этапу - созданию Скайнет крупнейшего в мире параллельного суперкомпьютера. По моему мнению Microsoft плохо понимает, насколько сильно они отстают.

Ладно. Надеюсь к этому моменту я убедил вас, что языки программирования с полноценными функциями дают больше возможностей для абстрагирования, что в свою очередь позволяет сделать код меньше, компактнее, упростить его повторное использование и масштабирование. Множество приложений Google используют MapReduce и если кто-то оптимизирует его или исправляет ошибки, то пользу получают все.

Позволю себе произнести банальность: наиболее производительны те среды программирования, которые позволяют вам работать на всех уровнях абстракции. Древний GW-BASIC вообще не позволял вам писать функции. В C есть указатели функций, но они уродливы и не анонимны, так что функции должны быть реализованы где-то еще, чтобы вы смогли использовать их. Java заставляет вас использовать функторы, которые еще уродливей. Как заметил Стив Йегг (Steve Yegge), Java - это королевство существительных.

Джоэл Сполски, вторник, 1 августа 2006
Can Your Programming Language Do This?


Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *


*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>