Редко простые задачки оборачиваются маленькими приключениями, но настройка отладчика RubyOnRails в NetBeans 6.8 как раз из их числа.
Итак, задача: на локальной машине отладить логику RoR приложения, соответствующую url-у www.a.line.ru/site_setup/forum.
Заходим в File → ProjectProperties(...) и видим там поле URL, а прямо под ним замечание в скобках (relative...). Не веря своим глазам, вписываем туда абсолютный адрес и проверяем, что он будет воспринят как относительный, например в нашем случае получится http://localhost:3005/www.a.line.ru/site _setup/forum. Разочаровавшись в NetBeans после пары попыток я пустился в хождения по форумам с целью понять, как избавиться от этого глупого ограничения. Оказалось, что проблему может решить только команда NetBeans, которая уже в курсе и все исправит в новой версии. Не сидеть же сложа руки тем, кому сейчас нужно работать и кого не устраивает один localhost?
Стал просматривать конфигурационные файлы NetBeans и тупо перебирать все пункты меню, чтобы найти хоть какую-то зацепку...И я кое-что нашел!
Во вкладке General пункта Tools → Options есть возможность выбора браузера... Вот если бы был браузер, умеющий подставлять вместо localhost произвольное имя - это бы решило проблему... и почему IE, FireFox и Chrome так делать не научили...
И тут в голове пронеслась мысль: "Ты же программист, возьми и научи". Сел и написал на C#:
Теперь дело за малым:
Итак, задача: на локальной машине отладить логику RoR приложения, соответствующую url-у www.a.line.ru/site_setup/forum.
Заходим в File → ProjectProperties(...) и видим там поле URL, а прямо под ним замечание в скобках (relative...). Не веря своим глазам, вписываем туда абсолютный адрес и проверяем, что он будет воспринят как относительный, например в нашем случае получится http://localhost:3005/www.a.line.ru/site
Стал просматривать конфигурационные файлы NetBeans и тупо перебирать все пункты меню, чтобы найти хоть какую-то зацепку...И я кое-что нашел!
Во вкладке General пункта Tools → Options есть возможность выбора браузера... Вот если бы был браузер, умеющий подставлять вместо localhost произвольное имя - это бы решило проблему... и почему IE, FireFox и Chrome так делать не научили...
И тут в голове пронеслась мысль: "Ты же программист, возьми и научи". Сел и написал на C#:
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace browser
{
class Program
{
static void Main(string[] args)
{
if (args.Length != 2) return;
var re = new Regex(@"^(.*?)localhost(.*?)\/([^\/]+)") ;
var url = args[0];
var browser = args[1];
var m = re.Match(url);
Func<int,string> f = i => m.Groups[i].Value;
if (m.Success) url = f(1) + f(3) + f(2);
Process.Start(
new ProcessStartInfo(browser, url)
{
UseShellExecute = false,
CreateNoWindow = true
}
);
}
}
} Теперь дело за малым:
- добавляем в Tools → Options новоиспеченный браузер с аргументами {URL} iexplore (к примеру)
- в File → ProjectProperties(...) пишем site_setup/forum/www.a.line.ru
Создатели Haskell понимали, что, даже с учетом всех плюсов функционального программирования, без хорошей поддержки побочных эффектов их язык вряд ли станет популярным, и в качестве решения выбрали монады.
Рассмотрим вычислитель с целыми константами и оператором целочисленного деления
data Term = Con Int | Div Term Term
eval :: Term → Int
eval(Con a) = a
eval(Div u v) = eval u / eval v
Вопрос: как его модифицировать, чтобы при делении на нуль генерировалось исключение?
Исключение - это побочный эффект, а Haskell - функциональный язык, поэтому ничего похожего на throw new DivException(...) просто так написать не получится.
Проявим изобретательность и перейдем от целых чисел к выражениям, принимающим одну из двух форм, числа или исключения, в зависимости от того, было деление на ноль или нет.
data M a = Raise Exception | Just a
type Exception = String
eval :: Term → M Int
eval(Con a) = Just a
eval(Div u v) = case eval u of
Raise e → Raise e
Just x →
case eval v of
Raise e → Raise e
Just y →
if b == 0 then Raise "divide by zero" else Just (x / y)
Все отлично, задача решена, но при такой реализации неудобно добавлять новые операторы - приходится писать одинаковые конструкции:
eval(Mul u v) = case eval u of
Raise e → Raise e
Just x →
case eval v of
Raise e → Raise e
Just y → Just (x * y)
Тут на помощь приходят монады :)
Определим два оператора, один - return, другой - bind
return :: a → M a
(>>=) :: M a → ( a → M b ) → M b
return x = Just x
(>>=) m g = case m of
Raise e → Raise e
Just x → g x
и перепишем вычислитель
eval(Div u v) = eval u >>= ( \ x → eval v >>= ( \ y → if y==0 then Raise "divide by zero" else Just (x / y) ) )
eval(Mul u v) = eval u >>= ( \ x → eval v >>= ( \ y → Just (x * y) ) )
Запись сократили, но то, что монады - это хорошее решение, пока что не очевидно. Также подумали разработчики Haskell и спрятали все лямбды и бинды за do-нотацией - синтаксическим сахаром, позволяющим писать код в императивном стиле:
eval(Div u v) = do {
x ← eval u;
y ← eval v;
if y==0 then Raise "divide by zero" else Just (x / y);
}
eval(Mul u v) = do {
x ← eval u;
y ← eval v;
return (x * y)
}
Для того, чтобы и на семантическом уровне не возникало проблем, все монады Haskell должны удовлетворять трем законам:
return не выполняет ни каких вычислений:
1. m >>= return = m //правая единица
2. return x >>= f = f x //левая единица
результат зависит от последовательности операторов, но не от их группировки
3. ( m >>= f ) >>= g = m >>= ( \ x → f x >>= g) //ассоциативность
Литература:
Рассмотрим вычислитель с целыми константами и оператором целочисленного деления
data Term = Con Int | Div Term Term
eval :: Term → Int
eval(Con a) = a
eval(Div u v) = eval u / eval v
Вопрос: как его модифицировать, чтобы при делении на нуль генерировалось исключение?
Исключение - это побочный эффект, а Haskell - функциональный язык, поэтому ничего похожего на throw new DivException(...) просто так написать не получится.
Проявим изобретательность и перейдем от целых чисел к выражениям, принимающим одну из двух форм, числа или исключения, в зависимости от того, было деление на ноль или нет.
data M a = Raise Exception | Just a
type Exception = String
eval :: Term → M Int
eval(Con a) = Just a
eval(Div u v) = case eval u of
Raise e → Raise e
Just x →
case eval v of
Raise e → Raise e
Just y →
if b == 0 then Raise "divide by zero" else Just (x / y)
Все отлично, задача решена, но при такой реализации неудобно добавлять новые операторы - приходится писать одинаковые конструкции:
eval(Mul u v) = case eval u of
Raise e → Raise e
Just x →
case eval v of
Raise e → Raise e
Just y → Just (x * y)
Тут на помощь приходят монады :)
Определим два оператора, один - return, другой - bind
return :: a → M a
(>>=) :: M a → ( a → M b ) → M b
return x = Just x
(>>=) m g = case m of
Raise e → Raise e
Just x → g x
и перепишем вычислитель
eval(Div u v) = eval u >>= ( \ x → eval v >>= ( \ y → if y==0 then Raise "divide by zero" else Just (x / y) ) )
eval(Mul u v) = eval u >>= ( \ x → eval v >>= ( \ y → Just (x * y) ) )
Запись сократили, но то, что монады - это хорошее решение, пока что не очевидно. Также подумали разработчики Haskell и спрятали все лямбды и бинды за do-нотацией - синтаксическим сахаром, позволяющим писать код в императивном стиле:
eval(Div u v) = do {
x ← eval u;
y ← eval v;
if y==0 then Raise "divide by zero" else Just (x / y);
}
eval(Mul u v) = do {
x ← eval u;
y ← eval v;
return (x * y)
}
Для того, чтобы и на семантическом уровне не возникало проблем, все монады Haskell должны удовлетворять трем законам:
return не выполняет ни каких вычислений:
1. m >>= return = m //правая единица
2. return x >>= f = f x //левая единица
результат зависит от последовательности операторов, но не от их группировки
3. ( m >>= f ) >>= g = m >>= ( \ x → f x >>= g) //ассоциативность
Литература:
- Monads for functional programming. Philip Wadler. 1992
- http://en.wikibooks.org/wiki/Haskell/Und
erstanding_monads
Функциональное программирование:
Нф любого терма зависит только от переменных и констант, лежащих в его основе, причем зависимость от переменных легко свести к вертикальной зависимости от констант:
если x - переменная ⇒ ∃ минимальный терм вида tλ = λ x . ( ... x ... ) и ∃ tβ = tλ y
если y - константа
доказано
иначе
y содержит переменные x1 ... xN
x1 - переменная ⇒ ∃ минимальный терм вида tλ1 = λ x1 . ( ... x1 ... ) и ∃ tβ1 = tλ1 y1 и так далее
...
высота синтаксического дерева ограничена ⇒ процесс сходится.
Вывод: в функциональных программах нет никаких зависимостей, кроме вертикальных.
Для полноты картины, сделаем одно отступление.
Императивное программирование:
Замечание: от переменных можно абстрагироваться и рассматривать любой ввод/вывод (например работать не с переменной, а с файлом).
Утверждение: В функциональном программировании (чего не скажешь об императивном) нет побочных эффектов.
Что из этого можно извлечь?
Несколько важных плюсов:
- программа - λ-терм
- исполнение программы - редукция исходного терма
Нф любого терма зависит только от переменных и констант, лежащих в его основе, причем зависимость от переменных легко свести к вертикальной зависимости от констант:
если x - переменная ⇒ ∃ минимальный терм вида tλ = λ x . ( ... x ... ) и ∃ tβ = tλ y
если y - константа
доказано
иначе
y содержит переменные x1 ... xN
x1 - переменная ⇒ ∃ минимальный терм вида tλ1 = λ x1 . ( ... x1 ... ) и ∃ tβ1 = tλ1 y1 и так далее
...
высота синтаксического дерева ограничена ⇒ процесс сходится.
Вывод: в функциональных программах нет никаких зависимостей, кроме вертикальных.
Для полноты картины, сделаем одно отступление.
Императивное программирование:
- программа - последовательность операторов, работающих с переменными
- переменные можно читать и записывать в соответствующих областях видимости
- области либо вложены друг в друга, либо не пересекаются
Замечание: от переменных можно абстрагироваться и рассматривать любой ввод/вывод (например работать не с переменной, а с файлом).
Утверждение: В функциональном программировании (чего не скажешь об императивном) нет побочных эффектов.
Что из этого можно извлечь?
Несколько важных плюсов:
- Отладка осуществляется за один проход вверх по дереву
- Дерево можно обсчитывать по слоям, вычисляя узлы каждого слоя параллельно
- Можно не вычислять узлы, не влияющие на конечный результат (агрессивная оптимизация, ленивое вычисление)
- При отладке на каждом уровне приходится заглядывать и ко всем левым братьям
- Не вертикальные зависимости препятствуют автоматическому распараллеливанию
- Не понятно, что является результатом, ведь все операторы имеют какой-то эффект.
Задача: написать λ-терм для итерационного процесса g(n, f, x)
g(n, f, x) = f ( g(n-1, f, x) )
g(0, f, x) = x
Решение: построим следующую рекурсивную конструкцию
g = λ n f x . zero? n x ( g ( pred n ) f ( f x ) )
Если n=0, результатом будет x, иначе (n>0) вернется g(n-1, f, f(x)). Семантика верная, но из-за явной рекурсии такой терм не свернуть к нормальной форме: получим бесконечное число замен.
Введем терм S = λ M n f x . zero? n x ( M ( pred n ) f ( f x ) ). Очевидно, что g - неподвижная точка S.
Оказывается, неподвижную точку всегда можно найти для любой абстракции, используя специальные термы - комбинаторы неподвижных точек. Примером таких комбинаторов является комбинатор Тьюринга:
Y = ( λ x y . y ( x x y ) )( λ x y . y ( x x y ) )
и это легко проверить:
Y M = Z Z M = (λ x y . y ( x x y ) ) Z M = M ( Z Z M ) = M ( Y M )
Всё! Можем писать ответ.
Ответ: g = Y S.
Замечание: Комбинаторы очень важны. Например в типизированном λ-исчислении нет комбинатора Тьюринга (нет нормальной формы и тип для него сразу не строится) и для полноты по Тьюрингу уже требуется дополнительная константа Yσ - комбинатор неподвижных точек типа σ. Своего рода "внешний" комбинатор :)
Литература:
g(n, f, x) = f ( g(n-1, f, x) )
g(0, f, x) = x
Решение: построим следующую рекурсивную конструкцию
g = λ n f x . zero? n x ( g ( pred n ) f ( f x ) )
Если n=0, результатом будет x, иначе (n>0) вернется g(n-1, f, f(x)). Семантика верная, но из-за явной рекурсии такой терм не свернуть к нормальной форме: получим бесконечное число замен.
Введем терм S = λ M n f x . zero? n x ( M ( pred n ) f ( f x ) ). Очевидно, что g - неподвижная точка S.
Оказывается, неподвижную точку всегда можно найти для любой абстракции, используя специальные термы - комбинаторы неподвижных точек. Примером таких комбинаторов является комбинатор Тьюринга:
Y = ( λ x y . y ( x x y ) )( λ x y . y ( x x y ) )
и это легко проверить:
Y M = Z Z M = (λ x y . y ( x x y ) ) Z M = M ( Z Z M ) = M ( Y M )
Всё! Можем писать ответ.
Ответ: g = Y S.
Замечание: Комбинаторы очень важны. Например в типизированном λ-исчислении нет комбинатора Тьюринга (нет нормальной формы и тип для него сразу не строится) и для полноты по Тьюрингу уже требуется дополнительная константа Yσ - комбинатор неподвижных точек типа σ. Своего рода "внешний" комбинатор :)
Литература:
- A short introduction to the Lambda Calculus. Achim Jung. 2004
Определение. λ-терм - слово, выводимое в грамматике:
Term =
Atom | Abstraction | Application
Atom =
Variable | Constant | ( Term )
Abstraction =
λ Variable . Term
Application =
Application Atom | Atom Atom
Variable =
x | y ...
Constant =
1 | 2 ... + | - ...
термы вида λ x .( λ y . (...)) часто сокращают до λ x y . (...)
Редукция:
Пример:
( ( λ a . ( λ b . + a b ) ( λ a x . * 2 a ) ) 3 ) 4
α=
( ( λ a . ( λ b . + a b ) ( λ c x . * 2 c ) ) 3 ) 4
η=
( ( λ a . ( λ b . + a b ) ( λ c . * 2 c ) ) 3 ) 4
β=
( λ b . + ( λ c . * 2 c ) b ) 3 ) 4
=
( ( λ b с . + * 2 c b ) 3 ) 4
β=
( λ c . + * 2 c 3 ) 4
β=
+ * 2 4 3 = 2*4 + 3 = 11
Теорема (о полноте λ-исчислений по Тьюрингу). Любую вычислимую ƒ : Z* → Z* можно записать как λ-терм и вычислить при помощи правил редукции, если в качестве констант использовать множество Z*∪{pred, succ, zero?}.
zero? 0 x y = x
zero? n x y = y ( n ≠ 0 )
succ 0 = 1
...
succ ( pred x ) = pred ( succ x ) = x
Можно обойтись и без Z*, если принять
0 = λ f x . x
1 = λ f x . f x
2 = λ f x . f ( f x )
...
Литература:
Term =
Atom | Abstraction | Application
Atom =
Variable | Constant | ( Term )
Abstraction =
λ Variable . Term
Application =
Application Atom | Atom Atom
Variable =
x | y ...
Constant =
1 | 2 ... + | - ...
термы вида λ x .( λ y . (...)) часто сокращают до λ x y . (...)
Редукция:
- α-преобразование - переименование параметра абстракции (применяется первой)
- β-редукция - подстановка значения параметра абстракции
- η-редукция - отбрасывание избыточной абстракции
Пример:
( ( λ a . ( λ b . + a b ) ( λ a x . * 2 a ) ) 3 ) 4
α=
( ( λ a . ( λ b . + a b ) ( λ c x . * 2 c ) ) 3 ) 4
η=
( ( λ a . ( λ b . + a b ) ( λ c . * 2 c ) ) 3 ) 4
β=
( λ b . + ( λ c . * 2 c ) b ) 3 ) 4
=
( ( λ b с . + * 2 c b ) 3 ) 4
β=
( λ c . + * 2 c 3 ) 4
β=
+ * 2 4 3 = 2*4 + 3 = 11
Теорема (о полноте λ-исчислений по Тьюрингу). Любую вычислимую ƒ : Z* → Z* можно записать как λ-терм и вычислить при помощи правил редукции, если в качестве констант использовать множество Z*∪{pred, succ, zero?}.
zero? 0 x y = x
zero? n x y = y ( n ≠ 0 )
succ 0 = 1
...
succ ( pred x ) = pred ( succ x ) = x
Можно обойтись и без Z*, если принять
0 = λ f x . x
1 = λ f x . f x
2 = λ f x . f ( f x )
...
Литература:
- A short introduction to the Lambda Calculus. Achim Jung. 2004
Совершенно естественно, что первым языком программирования высокого уровня стал императивный FORTRAN (созданный во второй половине 1950-х годов).
Когда перед вами стоят вычислительные задачи и есть только
Совершенно не понятно почему, но примерно в тоже время создатели языка Lisp работали над проблемами динамической типизации, списками, ассоциативными массивами, функциями высоких порядков и автоматической сборкой мусора, взяв за основу лямбда исчисления Чёрча, а не машину Тьюринга.
Большое дело - талантливые люди во главе с настоящим ученым.
Литература:
Когда перед вами стоят вычислительные задачи и есть только
- однородная память, содержащая и входные данные, и результаты вычислений
- команды, чтения, записи и выполнения действий (например арифметических)
- память отражает текущее состояние вычисления
- каждая следующая команда его изменяет (делает ближе к искомому результату)
Совершенно не понятно почему, но примерно в тоже время создатели языка Lisp работали над проблемами динамической типизации, списками, ассоциативными массивами, функциями высоких порядков и автоматической сборкой мусора, взяв за основу лямбда исчисления Чёрча, а не машину Тьюринга.
Большое дело - талантливые люди во главе с настоящим ученым.
Литература:
- History of Lisp. John McCarthy. 1979
- Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I. John McCarthy. 1960
- LISP 1.5 Programmer's Manual. John McCarthy. 1962