Re[8]: Почему ФЯ трудны для императивщиков
От: Lazy Cjow Rhrr Россия lj://_lcr_
Дата: 19.03.07 12:15
Оценка: 1 (1) +1
AVC,

AVC>А если серьезно, то как обойтись без goto мы знаем. Да и теорема Боэма-Джакопини есть.

AVC>А вот как обойтись вообще без побочных эффектов (ввод/вывод не в счет) — мы просто не знаем...

Вообще-то есть мнение, что

"State" is just a convenient concept for describing the set of parameters to a function. In imperative languages, that "state" is spread willy-nilly all around the program, and the history of the state has to be explicitly maintained. In a functional language, it is well specified.


Для ввода-вывода нужно лишь сохранение порядка вызова функций. Сохранение порядка — отнюдь не чуждая для ФП вещь, в частности то, что сформированный список будет [1,2,3], а не [3,2,1] и не [3,1,2] — вполне здравая и вполне формализуемая весчь. С вычислениями чуть сложнее, но из них тоже можно формировать последовательности и потом выполнять в заданной последовательности — продолжения, потоки, монады, или просто энергичная семантика — есть из чего выбрать.
quicksort =: (($:@(<#[),(=#[),$:@(>#[)) ({~ ?@#)) ^: (1<#)
Re[10]: Почему ФЯ трудны для императивщиков
От: palm mute  
Дата: 19.03.07 12:31
Оценка: 16 (2)
Здравствуйте, AVC, Вы писали:

AVC>Дальше разбирается, почему это было бы плохо.

AVC>В голову непосвященного, вроде меня, закрадывается крамольная мысль, что ФП не очень дружит с инкапсуляцией.

Инкапсуляция — недоопределенное понятие. Инкапсуляция — это сокрытие деталей реализации, но грань между деталями реализации и важными архитектурными решениями расплывчата. Если, например, где-то глубоко в недрах библиотеки, которую планируется использовать в программе под Unix, вызываются функции Win32, это никак нельзя назвать деталью реализации (хотя в публичном интерфейсе зависимость внутренностей от Win32 никак не отражается).

В ФП наличие побочных эффектов точно так же нельзя назвать деталью реализации. Если язык не отличает чистые выражения от выражений с побочными эффектами, автоматически возникает требование фиксированного порядка вычислений.

В чистом языке можно выразить ввод-вывод, присваивания и другие побочные эффекты, но выражения с побочными эффектами будут иметь другой тип.
Оператор присваивания, например, может иметь тип (Memory, Address, Value) -> (Memory).
Вычисление с состоянием может иметь тип (State, Argument) -> (State, Result).
У такого подхода есть плюсы: зависимости между выражениями выражены явно, поэтому независимые выражения можно вычислять в каком угодно порядке. Это не теоретизирование, данный факт используется в 1) STM для Haskell, для которого важно отсутствие побочных эффектов в транзакции 2) Data.Bytestring, например, использует loop fusion — техника оптимизации циклов с помощью эквивалентных преобразований выражений (несколько проходов по строке склеивается в один и т.д.)
Минусы: состояние передавать явно руками неудобно, плюс для эффективной реализации необходима гарантия, что программа имеет доступ к единственной копии состояния, тогда функцию f : State -> State можно реализовать как прямую запись в память. Обе проблемы решаются при помощи функций высшего порядка, протаскивающих состояние за кулисами, некоторой порции синтаксического сахара и мощной системы типов.
Re[10]: Почему ФЯ трудны для императивщиков
От: deniok Россия  
Дата: 19.03.07 13:06
Оценка:
Здравствуйте, AVC, Вы писали:

AVC>Здравствуйте, deniok, Вы писали:



AVC>Код a-la Oberon, но это совершенно неважно. Речь идет об обычном "порождающем" паттерне.

AVC>Главное, объекты какого-то типа создаются через фабричную процедуру.
AVC>Мы хотим ее поменять (прямо или косвенно переприсвоив значение fabrique), чтобы обращение к fabrique из произвольных мест программы приводило к созданию объектов иного конкретного типа, отличного от типа по умолчанию.
AVC>Как это делается в ФП?

Э, понимаешь, беда в том, что в чистом ФП ввиду отсутствия состояний с объектами тоже как-то не очень. Нету их. Поэтому фабрика может генерировать только функции. А это можно делать в любом количестве без всяких "порождающих" паттернов

-- Типа фабрика, генерирующая функции - прибавлятели числа n (на самом деле это обычное сложение (+) )
mkAdder n = \m -> n + m

-- Генерируем конкретный прибавлятель (семёрки) (на самом деле это сечение (7+) )
add7 = mkAdder 7
-- Другой конкретный прибавлятель (восьмёрки) (на самом деле это сечение (8+) )
add8 = mkAdder 8

-- сессия Hugs:
--   Main> add7 1
--   8 :: Int
--   Main> add7 2
--   9 :: Int
--   Main> add8 1
--   9 :: Int


Техника зовётся частичное применение. В общем виде

-- (1) "Фабрика"
--      Любая функция двух (или более) произвольных аргументов (типы аргументов t1 и t2, возвращаемое значение t3)
f::t1->t2->t3 

-- (2) нуль-арная функция (или более сложное выражение) подходящего типа
v1::t1


-- порождают в результате частичного применения 
g = f v1 -- унарную функцию типа g :: t2->t3 


-- которую потом можно применить к какому-нибудь 
v2::t2
g v2 --  получив результат типа t3


У функции всё что есть, так это сколько-то аргументов со своими типами и возвращаемое значение со своим.
Re[11]: Почему ФЯ трудны для императивщиков
От: palm mute  
Дата: 19.03.07 13:20
Оценка:
Здравствуйте, deniok, Вы писали:

D>Э, понимаешь, беда в том, что в чистом ФП ввиду отсутствия состояний с объектами тоже как-то не очень. Нету их. Поэтому фабрика может генерировать только функции. А это можно делать в любом количестве без всяких "порождающих" паттернов


Так весь фокус в том, что их есть! Точнее, можно сделать, если хочется. С сексуальными типами можно сделать хоть черта лысого.
Re[12]: Почему ФЯ трудны для императивщиков
От: deniok Россия  
Дата: 19.03.07 13:31
Оценка:
Здравствуйте, palm mute, Вы писали:

PM>Здравствуйте, deniok, Вы писали:


D>>Э, понимаешь, беда в том, что в чистом ФП ввиду отсутствия состояний с объектами тоже как-то не очень. Нету их. Поэтому фабрика может генерировать только функции. А это можно делать в любом количестве без всяких "порождающих" паттернов


PM>Так весь фокус в том, что их есть! Точнее, можно сделать, если хочется. С сексуальными типами можно сделать хоть черта лысого.


Дык о чём я выше писал здесь
Автор: deniok
Дата: 19.03.07
. Просто прежде чем вступать в интимные отношения с sexy types, нужно чётко понимать, что всё есть комбинатор, и SKI — исток его (ну или, что всё сводится к лямбде).
Re[12]: Почему ФЯ трудны для императивщиков
От: Mirrorer  
Дата: 19.03.07 21:33
Оценка: 10 (2)
Здравствуйте, AVC, Вы писали:

D>>скажем так: редкая птица долетит...


AVC>А потом все еще делают такие озабоченные лица и начинают разводить руками и спрашивать друг у друга: с чего это вдруг императивщикам трудно дается ФП?!


AVC>И потом, злые языки говорят, что монады (IO, списки и т.п.) — скрытый императив; но он, дескать, любит, чтобы его называли чистым ФП.

AVC>Вот и разберись...

Я попробую доступно разобрать

В объяснении будет использоваться псевдо-Хаскел. Все детали существенно упрощены.

В каком виде нам нужно состояние ?
Наверное в таком, чтобы можно было делать что-то наподобие такого
value <- getState   
putState (value + 1)   
value <- getState   
return value


Где функции getState и setState получают значение какой-то переменной и изменяют ее значение соответсвенно.

Представим нашу гипотетическую переменную как пару (переменная, значение)

Следовательно функции будут выглядеть следующим образом.

-- Ну понятно, да, плюем на переменную, нас интересует только состояние
getState (variable, state) = (_, state)

-- А здесь передаем пару, новое состояние, и возвращаем пару с той же переменной, но с новым состоянием.
putState (variable, state) newState = (variable, newState)


теперь попробуем переписать наш код с использованием функций
-- допустим это наша переменная а, со значением 2.
state = ("a",2)
-- получаем значение нашего состояние, то есть выполняем строчку
-- value <- getState   
let (initialVariable, initialState) = get state in
-- здесь мы изменяем значение нашей переменной на значение initialState + 1, то бишь 2+1 = 3
-- то есть выполняем строчку putState (value + 1)   
    let (initialVariable, changedState) = put (initialVariable, initialState) initialState + 1 in
-- это мы уже проходили, получаем измененное значение.
        let (initialVariable, finalState) = get (initialVariable, changedState) in
--     и возвращаем результат
           return finalState


Вуаля. Вот пример состояния в чисто функциональном стиле.
По сути ничего страшного. У нас есть пара (переменная, значение) на get мы возвращаем значение из этой пары, на set он же put — возвращаем новую пару (переменная, новое значение).
Синтаксис с let выражениями выглядит жутковато, и поэтому этот велосипед обсыпали сахаром и обозвали State.

Теперь по поводу IO. Там все не просто, а очень просто.
value <- readLine   
putState (value + 1)   
value <- getState   
writeLine value

Надеюсь идея понятна ?
Грубо говоря та же пара (variable, state)
только внутри variable находится служебная информация, типа хендлов открытых файлов, и тому подобных вещей, необходимых для реализации ввода-вывода. А в качестве state — строки, которые будут вводиться — выводиться, а также порядок их следования. Потому что очистить экран и написать строку, или написать строку и очистить экран это разные вещи

Такое ощущение что я что-то забыл. А ну да. Есть еще такая штука как Maybe.
Допустим появилось желание описать вычисления, каждое из которых может закончиться неудачей, причем если произошла неудача, то значение всего выражения считается тоже неудачей. Аналоги...
Ну допустим несколько транзакций в БД. Мы открываем их по очереди, и если где-то внутри произойдет ошибка, то все транзакции откатятся обратно.

Как это можно оформить. Опять же решение в лоб.
public enum TransctionState
{
    Success,
    Error
}
public class TransationResult
{
    public object Result;
    public TransationState State;
}
public class TransactionalCalculation
{
    public TransactionResult Combine(TransactionCalculation calculation)
    {
   // Выполняем свои дела
     TransactionResult res = this.Execute();
     // Если все завершилось успешно
                if(res.State == TransactionState.Success)
        //          вызываем следующее вычисление
                    res = calculation.Execute();
        return res;
    }
    // А это метод который будет выполняться в каждой транзакции.
    public TransactionResult Execute;
    // Предположим, что он инициализируется в конструкторе.
    public TransactionCalculation(MethodToExecute )
    {
        Execute = MethodToExecute;
    }
    
}

public void Main()
{
    // создаем три транзакции
    TransactionCalculation t1  = new TransactionCalculaction(doSomething);
    TransactionCalculation t2  = new TransactionCalculaction(doSomethingElse);
    TransactionCalculation t3  = new TransactionCalculaction(doYetAnotherThing);
    
  // комбинируем их в одну транзакцию.
    TransactionResult = t1.Combine(t2.Combine(t3));

 //Без метода Combine код выглядел бы примерно так
    TransactionResult res = t1.Execute()
    if(res == TransactionState.Success)
    {
        res = t2.Execute();
        if(res == TransactionState.Success)
        {
            res = t3.Execute();
        }
    }
}

[Сверхупрощение = ON]
Теперь прикинем, что же есть общего у State, IO, и TransactionCalculation ?
Ответ. У них есть методы
put и get
Для State это прямо таки put & get
Для IO это writeLine & readLine
Для TransactionCalculaction aka Maybe это Combine & Execute

Естественно прогарммитсы были бы не программисты, если бы не выделили такие повторяющиеся паттерны в интерфейс.
ICalculation
{
    put
    get
}

Чтобы единообразно работать и с состоянием, и с вводом-выводом, и с транзакционными вычислениями.
Но только назвали почему-то этот интерфейс монада. Наверное для того, чтобы народ пугать
[Сверхупрощение = OFF]

Вопросы, пожелания, корректировки, и прочая приветсвуются.

Скорее всего в посте куча несоответсвий, и возможно даже ошибок. Но я старался изложить идею максимально просто, без привлечения жуткого слова на М.
... << RSDN@Home 1.2.0 alpha rev. 676>>
Re[13]: Почему ФЯ трудны для императивщиков
От: AVC Россия  
Дата: 20.03.07 00:23
Оценка:
Здравствуйте, Mirrorer, Вы писали:

M>
M>-- Ну понятно, да, плюем на переменную, нас интересует только состояние
M>getState (variable, state) = (_, state)

M>-- А здесь передаем пару, новое состояние, и возвращаем пару с той же переменной, но с новым состоянием.
M>putState (variable, state) newState = (variable, newState)
M>


M>теперь попробуем переписать наш код с использованием функций

M>
M>-- допустим это наша переменная а, со значением 2.
M>state = ("a",2)
M>-- получаем значение нашего состояние, то есть выполняем строчку
M>-- value <- getState   
M>let (initialVariable, initialState) = get state in
M>-- здесь мы изменяем значение нашей переменной на значение initialState + 1, то бишь 2+1 = 3
M>-- то есть выполняем строчку putState (value + 1)   
M>    let (initialVariable, changedState) = put (initialVariable, initialState) initialState + 1 in
M>-- это мы уже проходили, получаем измененное значение.
M>        let (initialVariable, finalState) = get (initialVariable, changedState) in
M>--     и возвращаем результат
M>           return finalState
M>


M>Вуаля. Вот пример состояния в чисто функциональном стиле.

M>По сути ничего страшного. У нас есть пара (переменная, значение) на get мы возвращаем значение из этой пары, на set он же put — возвращаем новую пару (переменная, новое значение).
M>Синтаксис с let выражениями выглядит жутковато, и поэтому этот велосипед обсыпали сахаром и обозвали State.

Во-первых, спасибо за попытку объяснить суть дела простым человеческим языком.
Правда, я сильно туплю (может быть, час такой).
Насколько я понял, вложенность let существенна, иначе порядок действий (чисто императивное понятие) не будет соблюдаться.
Видимо, важно и то, что используется пара (переменная, значение), хотя и не вполне понял — почему.
Если бы не пара, я пожалуй брякнул бы что-нибудь вроде: ба! да это же старый добрый преинкремент ++state.
В смысле:
int state = 2;
...
++state;

Ведь, правда же, как иногда "работает" ++state:
1. загрузить значение переменной state в регистр;
2. увеличить это значение на единицу;
3. запомнить его обратно в state;
4. "вернуть" (т.е. использовать в выражении) новое значение.
Последовательность шагов, кажется, совпадает полностью.

Но существует одно качество, которое нельзя купить, — это надежность. Цена надежности — погоня за крайней простотой. Это цена, которую очень богатому труднее всего заплатить.

Хоар
Re[14]: Почему ФЯ трудны для императивщиков
От: AVC Россия  
Дата: 20.03.07 00:37
Оценка:
Здравствуйте, AVC, Вы писали:

Вопрос вдогонку.
Чем это принципиально отличается от
(define someOperation
  (let ((state 2))
    (lambda ()
      (set! state (+ state 1))
  state)))

в смысле функциональной "чистоты"?

Но существует одно качество, которое нельзя купить, — это надежность. Цена надежности — погоня за крайней простотой. Это цена, которую очень богатому труднее всего заплатить.

Хоар
Re[14]: Почему ФЯ трудны для императивщиков
От: Mirrorer  
Дата: 20.03.07 07:28
Оценка:
AVC>Насколько я понял, вложенность let существенна, иначе порядок действий (чисто императивное понятие) не будет соблюдаться.
Естественно. Мы же эмулируем императивность. Было бы странно если бы наша эмуляция не выполняла этого свойства

AVC>Если бы не пара, я пожалуй брякнул бы что-нибудь вроде: ба! да это же старый добрый преинкремент ++state.

Угумс. Ты все правильно понял это действительно аналог инкремента

AVC>Последовательность шагов, кажется, совпадает полностью.

+1
"Если Вы отличаетесь от меня, то это ничуть мне не вредит — Вы делаете меня богаче". Экзюпери
Re[15]: Почему ФЯ трудны для императивщиков
От: Mirrorer  
Дата: 20.03.07 07:28
Оценка:
Здравствуйте, AVC, Вы писали:

AVC>Чем это принципиально отличается от

AVC>
AVC>(define someOperation
AVC>  (let ((state 2))
AVC>    (lambda ()
AVC>      (set! state (+ state 1))
AVC>  state)))
AVC>

AVC>в смысле функциональной "чистоты"?
Хороший вопрос. Я сильно все упростил в первом посте, в основном потому что спать хотелось

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

interface ICalculation
{
    // Обрати особое внимание на то, что возвращает гет. 
    ICalculation get
    set
}

class Maybe : ICalculation
class State : ICalculation


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

Еще одна аналогия. Процессы Эрланга. Внутри процесса можно использовать побочные эффекты (через process dictionary) но на другие процессы это никак не повлияет, потому что они общаются исключительно сообщениями.

Такие дела.
"Если Вы отличаетесь от меня, то это ничуть мне не вредит — Вы делаете меня богаче". Экзюпери
Re[16]: Почему ФЯ трудны для императивщиков
От: deniok Россия  
Дата: 20.03.07 10:07
Оценка: 27 (3) +2
Здравствуйте, Mirrorer, Вы писали:

Следует отметить, что в рамках этой упрощённой интерпретации пока не видно инкапсуляции, с которой начался разговор. Помимо описанной здесь "структуры" состояний, нужно ещё задать императивную модель вычислений над ними. В ленивом языке, к которым относится Хаскелл нельзя написать последовательность вычислений в виде несвязанных инструкций
(положи 2 в а)
(вынь состояние из a, прибавь к нему 1, положи обратно)

поскольку порядок вычислений здесь не определен. В посте выше использовалось let-связывание, однако его тоже можно загнать внутрь монады. Фактически в Хаскелле используется перегрузка того, что в С-подобных языках используют в качестве разделителя инструкций, ";"
(положи 2 в а) ;
  (вынь состояние из a, прибавь к нему 1, положи обратно)

Только вместо ";" (которая, к сожалению, занята для других целей) пишут (>>=)
(положи 2 в а) >>=
  (вынь состояние из a, прибавь к нему 1, положи обратно)

Оператор (>>=) связывает два вычисления, позволяя неявно протаскивать всё что угодно из первого вычисления во второе (его поэтому и произносят как bind). Для монады состояния это будет состояние. Собственно реализация оператора (>>=) и есть главное в задании монады. Как его опишешь — такая монада и получится.
Re[17]: Почему ФЯ трудны для императивщиков
От: palm mute  
Дата: 20.03.07 10:40
Оценка: 15 (2) +1
D>Оператор (>>=) связывает два вычисления, позволяя неявно протаскивать всё что угодно из первого вычисления во второе (его поэтому и произносят как bind).

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

Конструкция
let x=expression1 in expression2

связывает (binds) переменную x с результатом вычисления выражения expression1 в контексте выражения expression2.

Конструкция
computation1 >>= \x -> computation2

, которая под сахаром выглядит как
do x <- computation1
   computation2


связывает (binds) переменную x с результатом вычисления computation1 в контексте computation2.

Из-за этой аналогии let и bind последний оператор так и называется.
Re[17]: И снова о них, теплых и расплывчатых
От: palm mute  
Дата: 20.03.07 12:25
Оценка: +1
Здравствуйте, deniok, Вы писали:

D>Оператор (>>=) связывает два вычисления, позволяя неявно протаскивать всё что угодно из первого вычисления во второе (его поэтому и произносят как bind). Для монады состояния это будет состояние. Собственно реализация оператора (>>=) и есть главное в задании монады. Как его опишешь — такая монада и получится.


Перегрузка точки с запятой — отличная метафора. Осталось от пугающего слова "монада" избавиться. На самом деле, определяя оператор >>=, мы конструируем виртуальную машину, которая умеет выполнять программы вида:

do { x <- foo;
     y <- bar x;
     return (baz (x, y)) }


1) Как работает этам виртуальная машина, определяем мы сами, перегружая оператор ";"
2) Определяем мы ее на чистом ФЯ, поэтому, несмотря на императивный вид, она остается чисто функциональной.
Re[18]: И снова о них, теплых и расплывчатых
От: deniok Россия  
Дата: 20.03.07 13:04
Оценка:
Здравствуйте, palm mute, Вы писали:

PM>1) Как работает этам виртуальная машина, определяем мы сами, перегружая оператор ";"

PM>2) Определяем мы ее на чистом ФЯ, поэтому, несмотря на императивный вид, она остается чисто функциональной.

Вот у меня возник вопрос — императивную модель вычислений мы, понятно, сконструировать можем. А прологовскую, с бэктрэкингом? Я уверен, что можно, но явных ссылок что-то не нашёл.
Re[19]: И снова о них, теплых и расплывчатых
От: palm mute  
Дата: 20.03.07 13:18
Оценка: 8 (1)
Здравствуйте, deniok, Вы писали:

D>Вот у меня возник вопрос — императивную модель вычислений мы, понятно, сконструировать можем. А прологовскую, с бэктрэкингом? Я уверен, что можно, но

явных ссылок что-то не нашёл.

Самый простой пример — монада List, она же list comprehension. Есть более сложные логические монады в исполнении Олега Киселева и Кена Шана, но я с ними не разбирался:
http://okmij.org/ftp/papers/LogicT.pdf
Re[19]: И снова о них, теплых и расплывчатых
От: Mirrorer  
Дата: 20.03.07 13:24
Оценка:
Здравствуйте, deniok, Вы писали:

D> императивную модель вычислений мы, понятно, сконструировать можем. А прологовскую, с бэктрэкингом?


Ну это, через жо... в смысле через императивную модель. Как вариант
А по сути мне кажется что это будет нечто похожее на монаду Maybe...
"Если Вы отличаетесь от меня, то это ничуть мне не вредит — Вы делаете меня богаче". Экзюпери
Re[15]: Почему ФЯ трудны для императивщиков
От: AVC Россия  
Дата: 20.03.07 15:36
Оценка:
Здравствуйте, Mirrorer, Вы писали:

AVC>>Если бы не пара, я пожалуй брякнул бы что-нибудь вроде: ба! да это же старый добрый преинкремент ++state.

M>Угумс. Ты все правильно понял это действительно аналог инкремента

Я "зацеплюсь" за этот пункт, наверное, далеко не самый важный.
Когда смотришь (как я) на ФП со стороны, идея "прозрачности по ссылкам" и ее потенциальных выгод усваивается очень легко.
А вот как может быть "чисто функциональным" аналог оператора ++i, как раз и непонятно.
Возможно, это смешные трудности. Но я, как императивщик, вижу их именно здесь.
Что-то вроде: "Какое же это чистое ФП?! Это же xxx!"

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

Но существует одно качество, которое нельзя купить, — это надежность. Цена надежности — погоня за крайней простотой. Это цена, которую очень богатому труднее всего заплатить.

Хоар
Re[16]: Почему ФЯ трудны для императивщиков
От: palm mute  
Дата: 20.03.07 22:13
Оценка: 15 (2)
Здравствуйте, AVC, Вы писали:


AVC>Я "зацеплюсь" за этот пункт, наверное, далеко не самый важный.

AVC>Когда смотришь (как я) на ФП со стороны, идея "прозрачности по ссылкам" и ее потенциальных выгод усваивается очень легко.
AVC>А вот как может быть "чисто функциональным" аналог оператора ++i, как раз и непонятно.

Я, честно говоря, не совсем внимательно следил за тем, как вы пришли к оператору инкремента. Но я хочу показать пример, как код, похожий внешне на императивный, может быть чисто функциональным. Пример будет на С++, монад не будет (чудес тоже). Итак, представим, что у нас есть некий игрушечный процессор, имеющий один регистр, и поддерживающий операции:
inc/dec — инкремент/декремент регистра
set — установка нового значения регистра
rand — запись в регистр псевдо-случайного значения
print — печать текущего значения в регистре

#include <iostream>
#include <sstream>

using namespace std;

template <class Machine>
void imperative_program(Machine machine)
{
    cout << machine
    // here's imperative program for our VM
        .inc()
        .inc()        
        .print() // outputs 2

        .dec()        
        .print() // outputs 1
     
        .inc()
        .inc()
        .inc()
        .print() // outputs 4

        .set(75)
        .print() // outputs 75

        .rand()
        .print() // outputs random number
        .rand()
        .print() // outputs another random number

   /////////////////////////////////////////
        .output;
}

string to_string(int x)
{
    ostringstream oss;
    oss << x;
    return oss.str();
}

int rand(int seed)
{
    const int a = 1664525;
    const int b = 1013467;
    const int m = 65536;
    return (a * seed + b) % m;
}


// Implementation with destructive updates

struct StatefulMachine
{
    int counter;
    string output;

    StatefulMachine () : counter(0) {}

    StatefulMachine& inc() { ++counter; return *this; }
    StatefulMachine& dec() { --counter; return *this; }
    StatefulMachine& set(int x) { counter=x; return *this; }
    StatefulMachine& rand() { counter=::rand(counter); return *this; }
    

    StatefulMachine& print()
    {
        output += to_string(counter) + "\n";
        return *this;
    }
};


// Purely functional implementation

struct StatelessMachine
{
    const int counter;
    const string output;

    StatelessMachine(int a_counter=0, string a_output="") :
        counter(a_counter),
        output(a_output) {}

    StatelessMachine inc() const { return StatelessMachine(counter+1, output); }
    StatelessMachine dec() const { return StatelessMachine(counter-1, output); }
    StatelessMachine set(int x) const { return StatelessMachine(x, output); }
    StatelessMachine rand() const  { return StatelessMachine(::rand(counter), output); }
    StatelessMachine print() const { return StatelessMachine(counter, 
                                                             output+to_string(counter)+"\n"); }
};

int main()
{ 
    imperative_program(StatefulMachine());
    imperative_program(StatelessMachine());

    return 0;
}
Re[17]: Почему ФЯ трудны для императивщиков
От: deniok Россия  
Дата: 20.03.07 22:27
Оценка:
Здравствуйте, palm mute, Вы писали:

Ага BRules Олега Киселёва в действии
Re[16]: Почему ФЯ трудны для императивщиков
От: geniepro http://geniepro.livejournal.com/
Дата: 20.03.07 23:26
Оценка:
Здравствуйте, AVC, Вы писали:

AVC> А вот как может быть "чисто функциональным" аналог оператора ++i, как раз и непонятно.

AVC> Возможно, это смешные трудности. Но я, как императивщик, вижу их именно здесь.
AVC> Что-то вроде: "Какое же это чистое ФП?! Это же xxx!"

Как говорят итальянцы, приветствуя друг друга: "Чао!"

Упрощённо говоря, идея в том, что бы при каждом изменении состояния переменной x, создавать новую область видимости переменных, в ней создавать новую переменную с таким же именем x и изменённым значением переменной x из предыдущей области видимости. В монадах такие новые области видимости создаются определением вложенных функций. Например:
main = do x <- return 1
          print x
          x <- return 2
          print x
          x <- return (x + 3)
          print x

выведет, конечно же, 1, затем 2, датем 5 (2+3).
Это выглядит императивно, так почему же это на самом деле функционально чистый код?
А вот почему: конструкция do раскручивается в несколько вложенных друг в друга безымянных функций, у каждой из которых есть параметр x. Почти буквально это будет так:
main = let unnamed1 x =
              (print x) >>
              let unnamed2 x = 
                     (print x) >>
                     let unnamed3 x =
                            print x
                     in  return (x + 3) >>= unnamed3
              in  return 2 >>= unnamed2
       in  return 1 >>= unnamed1

Раскрытие do-выражения получилось страшным. Упрощённо в Scheme можно представить так:
(define (main)
  (define (unnamed1 x)
    (define (unnamed2 x)
      (define (unnamed3 x)
        (printf "~a~n" x))
      (printf "~a~n" x)
      (unnamed3 (+ x 3)))
    (printf "~a~n" x)
    (unnamed2 2))
  (unnamed1 1))

Состояние переменной x постоянно протаскивается через параметр x от одной функции к другой и меняется, но при этом никаких деструктивных операций не происходит, в отличие от ++x. Присваивание везде однократное!

Вот так вот и получается, что в монаде чистые функциональные действия, хотя в do-нотации они выглядят императивно...

Ах да, так как тут имеется вывод, то есть и императивная операция — print. Но тут уж ничего не попишешь, состояние изображения на мониторе меняется императивно... :о)

Как говорят те же итальянцы, прощаясь друг с другом: "Чао!" :о))
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.