Немного поясните про чистую функциональщину
От: johny5 Новая Зеландия
Дата: 28.08.07 04:20
Оценка:
На своём пути к функциональному миру, читаю различные книжки, пока остаётся загадкой пара вопросов. Если можно, поясните.

В чисто функциональном языке (возьмём для примера Хаскель) все переменные есть константы, функции детерминированы — результат вызова зависит только от её аргументов.

1. Допустим мы имеем функцию f(arg), которая детерминирована, делает свою работу и делает её медленно. Мы хотим приделать кэш вызовов. Собственно вопрос, где хранить состояние — тот самый кэш?

2. Как возможно определить и вообще работать с явно недетерминированными функциями типа rand()?



28.08.07 08:42: Перенесено модератором из 'Философия программирования' — Odi$$ey
Re: Немного поясните про чистую функциональщину
От: awson  
Дата: 28.08.07 05:33
Оценка:
Здравствуйте, johny5, Вы писали:

J>1. Допустим мы имеем функцию f(arg), которая детерминирована, делает свою работу и делает её медленно. Мы хотим приделать кэш вызовов. Собственно вопрос, где хранить состояние — тот самый кэш?

Как и в любом императивном — Map, Array, List

J>2. Как возможно определить и вообще работать с явно недетерминированными функциями типа rand()?

С такой сигнатурой:
rand :: StateType -> (Int, StateType)
функция становится детерминированной. Если само значение состояния (типа StateType) нам не нужно, остается только его тип. Так получаются монады.
Re: Немного поясните про чистую функциональщину
От: Sinclair Россия https://github.com/evilguest/
Дата: 28.08.07 05:35
Оценка: 1 (1)
Здравствуйте, johny5, Вы писали:


J>На своём пути к функциональному миру, читаю различные книжки, пока остаётся загадкой пара вопросов. Если можно, поясните.


J>В чисто функциональном языке (возьмём для примера Хаскель) все переменные есть константы, функции детерминированы — результат вызова зависит только от её аргументов.


J>1. Допустим мы имеем функцию f(arg), которая детерминирована, делает свою работу и делает её медленно.

в чистом ФП функция не может делать никакой работы. Это всего лишь декларация соответствия результата параметрам. Поэтому о скорости ее работы говорить не имеет смысла.
Это реализация делает некоторую работу по собственно вычислению всего, чего надо.
J>Мы хотим приделать кэш вызовов. Собственно вопрос, где хранить состояние — тот самый кэш?
Среда исполнения и должна решать, что такое кэш и что такое вызов.
Умные компиляторы ухитряются, к примеру, экономить вызовы при переборе чисел Фибоначчи, так, чтобы вместо O(2^n) каждое число отдавалось за О(1). Именно благодаря использованию чего-то вроде кэша вызовов.

J>2. Как возможно определить и вообще работать с явно недетерминированными функциями типа rand()?

Приходится вводить состояние. Функции типа rand на самом деле жестко детерминированы. В процедурном программировании состояние rand хранится в скрытой глобальной переменной; в ООП — в экземпляре ГСЧ. В ФП придется передавать состояние явно (либо воспользоваться монадами или еще каким-нибудь мега-способом): rand(current_state)->(random, next_state).
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Немного поясните про чистую функциональщину
От: johny5 Новая Зеландия
Дата: 28.08.07 05:51
Оценка:
A>С такой сигнатурой:
A>rand :: StateType -> (Int, StateType)
A>
функция становится детерминированной. Если само значение состояния (типа StateType) нам не нужно, остается только его тип. Так получаются монады.


Интересно, а, например, в Clean нет никаких монад, и как там?
Re[3]: Немного поясните про чистую функциональщину
От: BulatZiganshin  
Дата: 28.08.07 06:20
Оценка: 1 (1) +1
Здравствуйте, johny5, Вы писали:

A>>С такой сигнатурой:
A>>rand :: StateType -> (Int, StateType)
A>>
функция становится детерминированной. Если само значение состояния (типа StateType) нам не нужно, остается только его тип. Так получаются монады.


J>Интересно, а, например, в Clean нет никаких монад, и как там?


в clean каждая императивная операция получает и возвращает доп. аргумент, который рассматривается как "состояние мира"

монады сами по себе — это только способ декларации императивности. реализация же их либо использует тот же фокус с "состоянием мира" (в большинстве компиляторов), либо continuations (в hbc). почитай http://haskell.org/haskellwiki/IO_inside
Люди, я люблю вас! Будьте бдительны!!!
Re: Немного поясните про чистую функциональщину
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 28.08.07 07:06
Оценка:
Здравствуйте, johny5, Вы писали:

J>В чисто функциональном языке (возьмём для примера Хаскель) все переменные есть константы, функции детерминированы — результат вызова зависит только от её аргументов.


В чисто функциональном языке вообще нет переменных. Есть только именованные значения. Это сахар, вообще можно писать в чисто функциональном стиле на одних комбинаторах, но это не удобно

J>1. Допустим мы имеем функцию f(arg), которая детерминирована, делает свою работу и делает её медленно. Мы хотим приделать кэш вызовов. Собственно вопрос, где хранить состояние — тот самый кэш?


Как уже писал Sinclair, об этом должен заботиться компилятор. Причём, ИМХО, это должен делать не только компилятор, но и рантайм, который считает статистику вызовов и т.д., т.к. на этапе компиляци не всегда можно предсказать такие вещи.
... << RSDN@Home 1.2.0 alpha rev. 710>>
Re[2]: Немного поясните про чистую функциональщину
От: johny5 Новая Зеландия
Дата: 28.08.07 07:17
Оценка:
J>>1. Допустим мы имеем функцию f(arg), которая детерминирована, делает свою работу и делает её медленно.
S>в чистом ФП функция не может делать никакой работы. Это всего лишь декларация соответствия результата параметрам. Поэтому о скорости ее работы говорить не имеет смысла.
S>Это реализация делает некоторую работу по собственно вычислению всего, чего надо.
J>>Мы хотим приделать кэш вызовов. Собственно вопрос, где хранить состояние — тот самый кэш?
S>Среда исполнения и должна решать, что такое кэш и что такое вызов.

Т.е. реализовать ручное кэширование в случае необходимости будет невозможно?
Re[3]: Немного поясните про чистую функциональщину
От: DrDred Россия  
Дата: 28.08.07 07:38
Оценка:
Здравствуйте, johny5, Вы писали:


J>>>1. Допустим мы имеем функцию f(arg), которая детерминирована, делает свою работу и делает её медленно.

S>>в чистом ФП функция не может делать никакой работы. Это всего лишь декларация соответствия результата параметрам. Поэтому о скорости ее работы говорить не имеет смысла.
S>>Это реализация делает некоторую работу по собственно вычислению всего, чего надо.
J>>>Мы хотим приделать кэш вызовов. Собственно вопрос, где хранить состояние — тот самый кэш?
S>>Среда исполнения и должна решать, что такое кэш и что такое вызов.

J>Т.е. реализовать ручное кэширование в случае необходимости будет невозможно?


Просто насколько я понимаю, для ленивого языка, коим является Haskell, само понятие "кэширование результатов вызова функции" является неприменимым, т.к. мы не знаем, в какой точно момент понадобится результат вычисления, и соответственно, когда этот результат будет вычислен. В принципе, можно получить желаемый результат, путем strict вычисления нужной функции, но вот нужно ли это...
Для строгих языков, типа OCaml, подобное вполне реализуемо, вот например обсуждение...
--
WBR, Alexander
Re[3]: Немного поясните про чистую функциональщину
От: BulatZiganshin  
Дата: 28.08.07 09:12
Оценка: 35 (3)
J>Т.е. реализовать ручное кэширование в случае необходимости будет невозможно?

это можно сделать двумя способами:
— использовать lazy семантику вычисления. фактически, laziness — это само по себе система кеширования однажды вычисленных значений, надо просто уметь ею управлять
— использовать unsafePerformIO

что касается первого — вот типичный пример из базовой библиотеки GHC:

power x n | x==2 && (n>=0 && n<=317) = powers2!n
          | otherwise                = x^n

powers2 :: Array Int Double
powers2 = array (0,318) [2^n | n <- [0..317]]


благодаяр laziness, значения в массиве powers2 вычисляются только по необходимости и запоминаются. степени же, не включённые в этот массив, вычисляются каждый раз заново. для более сложных ключей аналогиынйм образом можно прменить Map или Trie
Люди, я люблю вас! Будьте бдительны!!!
Re[4]: Немного поясните про чистую функциональщину
От: BulatZiganshin  
Дата: 28.08.07 09:18
Оценка:
DD>Просто насколько я понимаю, для ленивого языка, коим является Haskell, само понятие "кэширование результатов вызова функции" является неприменимым, т.к. мы не знаем, в какой точно момент понадобится результат вычисления, и соответственно, когда этот результат будет вычислен.

порядок вычисления в хаскеле определён. недавно об этом рассказывали в cafe. способ вычисления — graph reduction, т.е. подставновка вместо левой части определения функции правой части. при этом вычисляется всегда самый наружный вызов, для которого уже достаточно данных

вот кусочек из моего письма:

simple example: consider evaluation of "head [1..]". [1..] may be
represented with the following recursive function:

list n = n:list n+1

so we have "head (list 1)" where head defined as

head (x:_) = x

let's evaluate expression:

head (list 1) =>
head (1:list 2) =>
1

as you see, "list 2" was just dropped during evaluation.


но лучше поглядеть в оригинале — там высказывались настоящие теоретики, не мне чета
Люди, я люблю вас! Будьте бдительны!!!
Re: offtop
От: Константин Л. Франция  
Дата: 28.08.07 09:34
Оценка: :))
Здравствуйте, johny5, Вы писали:

[]

че то меня начинает раздражать эта мода на "чистую" функциональщину. Эдакий гламур в программинге, от которого в чистом виде нету ничего хорошего.
Re: Немного поясните про чистую функциональщину
От: Кодт Россия  
Дата: 28.08.07 10:05
Оценка: 35 (3)
Здравствуйте, johny5, Вы писали:

J>В чисто функциональном языке (возьмём для примера Хаскель) все переменные есть константы, функции детерминированы — результат вызова зависит только от её аргументов.


Хорошо бы ещё взять для примера ML — потому что один ленивый, второй энергичный. (Надо сказать, каждый из них умеет делать и то, и другое).

J>1. Допустим мы имеем функцию f(arg), которая детерминирована, делает свою работу и делает её медленно. Мы хотим приделать кэш вызовов. Собственно вопрос, где хранить состояние — тот самый кэш?


Есть три разных способа редукции выражений.
Какая-то там теорема Чёрча показывает, что все эти способы — возможно, за разное время (вплоть до бесконечного ), — приводят к одному результату.
f(x) = expr(....., x, ....., x, .....)  -- аргумент используется несколько раз
f(g()) -- подставим туда нечто вычисляемое

-- подставить копии (лямбда-исчисление на строках)
f(g())
= expr(....., g(), ....., g(), .....)

-- вычислить и подставить <ссылку на> результат (путь ML)
f(g())
= let v=g() in f(v)
= let v=g() in expr(....., v, ....., v, .....)

-- подставить ссылку на одно и то же ленивое значение (путь Haskell)
-- которое вычислится единожды, когда потребуется первый раз
f(g())
= expr(....., v, ....., v, .....) where v=g()

Поэтому ни в ML, ни в Haskell не нужно делать фанатичную мемоизацию.

Другое дело, что если написать
let
    x = g()
    y = g()
in f(x,y)

то компилятор+рантайм может догадаться, а может не догадаться, что x==y и, соответственно, выполнит редукцию g() единожды или дважды.
Вот тут уже нужно смотреть, стоит ли доверять интеллекту компилятора и объёму кэша рантайма, или переписать руками.

Говорят, что некоторые диалекты Пролога работают, как суперкомпьютер Крей-1: вычисляют бесконечный цикл за 6 секунд.
% Question of Life, Universe and Everything
qlue(X) :- qlue(X).
qlue(42).

?- qlue(N)
% благодаря мемоизации распознано зацикливание, и второй раз уже машина поехала по второй ветке
N=42
Yes

Хотя для большинства реализаций это не так.

J>2. Как возможно определить и вообще работать с явно недетерминированными функциями типа rand()?


Во-первых, можно предположить, что функция всё-таки детерминирована
rand :: RandSeed -> (Int, RandSeed)
srand :: Int -> RandSeed

r0 = srand 0
(x1,r1) = rand r0
(x2,r2) = rand r1

-- осталось только протащить нитку из последовательно вычисляемых r_k через все функции
-- например, 
randoms :: Int -> RandSeed -> ([Int], RandSeed)
randoms 0 r0 = ([],r0)
randoms n r0 =
    let
        (x,r1) = rand r0
        (xs,rn) = randoms r1
    in ((x:xs),rn)

(xs1,_) = randoms r2
(xs2,_) = randoms r2 -- новая серия (повторяет предыдущую)

Это подход монады State.

Во-вторых, недетерминированная функция — это функция со скрытым параметром World, природа которого не поддаётся расшифровке.
Монада IO в Хаскеле — это, по сути, монада State, только намертво засахаренная (чтобы нельзя было хакнуть вселенную и запустить два параллельных потока вычислений над копиями мира).
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re[2]: Немного поясните про чистую функциональщину
От: BulatZiganshin  
Дата: 28.08.07 10:18
Оценка:
К>Хорошо бы ещё взять для примера ML — потому что один ленивый, второй энергичный. (Надо сказать, каждый из них умеет делать и то, и другое).

по-моему, не столько делать, сколько эмулировать через всякие "состояния мира"

К>Другое дело, что если написать

К>
К>let
К>    x = g()
К>    y = g()
К>in f(x,y)
К>

К>то компилятор+рантайм может догадаться, а может не догадаться, что x==y и, соответственно, выполнит редукцию g() единожды или дважды.
К>Вот тут уже нужно смотреть, стоит ли доверять интеллекту компилятора и объёму кэша рантайма, или переписать руками.

дело не в интеллекте. представь себе, что результат g() — список из миллиарда элементов, а f=(++) т.е. безопасно соптимизировать это можно только если x имеет примитивный тип (и следовательно его значение не превосходит по занимаемой памяти thunk для вычисления g() )
Люди, я люблю вас! Будьте бдительны!!!
Re[2]: offtop
От: BulatZiganshin  
Дата: 28.08.07 10:19
Оценка: +1
Здравствуйте, Константин Л., Вы писали:

КЛ>Здравствуйте, johny5, Вы писали:


КЛ>[]


КЛ>че то меня начинает раздражать эта мода на "чистую" функциональщину. Эдакий гламур в программинге, от которого в чистом виде нету ничего хорошего.


а мода на применение компьютеров уже не раздражает?
Люди, я люблю вас! Будьте бдительны!!!
Re: Немного поясните про чистую функциональщину
От: VladD2 Российская Империя www.nemerle.org
Дата: 28.08.07 11:48
Оценка: -1
Здравствуйте, johny5, Вы писали:

J>В чисто функциональном языке ...


Нет таких языков. Разве что лямбда исчисления Чёрча, но это не язык программирования.

Во всех остальных состояние в том или ином виде (зачастую очень завуалированном) есть. Кое что умеют делать компиляторы (оптимизировать). Где-то используется магия вроде монад.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Немного поясните про чистую функциональщину
От: Кодт Россия  
Дата: 28.08.07 12:47
Оценка: +1 :))
Здравствуйте, BulatZiganshin, Вы писали:

К>>то компилятор+рантайм может догадаться, а может не догадаться, что x==y и, соответственно, выполнит редукцию g() единожды или дважды.

К>>Вот тут уже нужно смотреть, стоит ли доверять интеллекту компилятора и объёму кэша рантайма, или переписать руками.

BZ>дело не в интеллекте. представь себе, что результат g() — список из миллиарда элементов, а f=(++) т.е. безопасно соптимизировать это можно только если x имеет примитивный тип (и следовательно его значение не превосходит по занимаемой памяти thunk для вычисления g() )


Вот именно, что в интеллекте.
Глупый рантайм — не мемоизирует вообще ничего, расходуя время.
Деятельно-глупый — мемоизирует всё подряд, расходуя память (и время тоже).
Умный — пребывает в дао, находя равновесие между крайностей.
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re[2]: Немного поясните про чистую функциональщину
От: no4  
Дата: 28.08.07 13:47
Оценка:
Здравствуйте, VladD2, Вы писали:

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


J>>В чисто функциональном языке ...


VD>Нет таких языков. Разве что лямбда исчисления Чёрча, но это не язык программирования.


VD>Во всех остальных состояние в том или ином виде (зачастую очень завуалированном) есть.

VD>Кое что умеют делать компиляторы (оптимизировать). Где-то используется магия вроде монад.

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

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

По-моему, так и делается в clean, нанапример.

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

Таким образом, они оба чистые.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[3]: Немного поясните про чистую функциональщину
От: BulatZiganshin  
Дата: 28.08.07 15:15
Оценка:
no4>Соответственно, чтобы язык был чисто функциональный надо, чтобы состояние было в качестве аргумента и в качестве результата.

no4>По-моему, так и делается в clean, нанапример.


это не состояние, а фиктивный аргумент, пощволяющий управлять последоватенльностью исполнения. подробности в http://haskell.org/haskellwiki/IO_inside

no4>А IO монада в haskell, насколько я понял, состоит в том, что функция возвращает не состояние, а функуцию, которая для текущего состояние мира вычисляет действие с ним. А использование этой функции происходит за пределами языка haskell.


я чуть выше написал — реализация монады IO в большинстве хаскеловских компиляторов точно такая же, как в clean. разница только в том, что в clean это *спецификация*, а в хаскеле — одна из возможных *реализаций*
Люди, я люблю вас! Будьте бдительны!!!
Re[3]: Немного поясните про чистую функциональщину
От: VladD2 Российская Империя www.nemerle.org
Дата: 29.08.07 03:40
Оценка: -1 :))
Здравствуйте, no4, Вы писали:

no4>По-моему, чистый функциональный язык не тот, где нет состояние, а тот, где нет побочных эффектов.


Если есть состояние, то его надо менять. Изменение дает побочные эффекты.

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

no4>А IO монада в haskell, насколько я понял, состоит в том, что функция возвращает не состояние, а функуцию, которая для текущего состояние мира вычисляет действие с ним. А использование этой функции происходит за пределами языка haskell.


no4>Таким образом, они оба чистые.


Ага щас блин. А разные ансэйфперформио и т.п.?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Немного поясните про чистую функциональщину
От: Курилка Россия http://kirya.narod.ru/
Дата: 29.08.07 04:34
Оценка: +1
Здравствуйте, VladD2, Вы писали:

VD>В любмо случае языков без побочных эффектов тоже небывает. Тот же Хаскель содержит опасные манады.


Влад, можно пояснить, что ты подразумеваешь под выделенным? Откуда взят этот термин?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.