3

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

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

Предположим, что у нас есть переменная x, которая представляет общее количество HTTP-запросов, сделанных программой. Если у нас есть два потока, я хочу, чтобы потоки увеличивали x всякий раз, когда HTTP-запрос был создан любым потоком. Если оба потока создают другую копию переменной x, то как они могут синхронизировать значение x. Например: если поток 1 делает 10 HTTP-запросов и поток 2 сделал 11 HTTP-запросов, тогда они будут печатать 10 и 11 соответственно, но как бы я напечатал 21.

+3

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

ответ

1

Я обращусь к части Haskell. MVar является одним из механизмов связи для потоков. Это один из примера, взятого из книги Саймона Марлоу (программа само за себя):

main = do 
    m <- newEmptyMVar 
    forkIO $ do putMVar m 'x'; putMVar m 'y' 
    r <- takeMVar m 
    print r 
    r <- takeMVar m 
    print r 

Выход для вышеуказанной программы будет:

'x' 
'y' 

Вы можете увидеть в приведенном выше примере как значение MVar в переменной m разделяется между потоками. Вы можете узнать больше об этих методах в this book.

+0

-1 для устранения реальной проблемы - неправильное представление о том, как использовать потоки, особенно на языке Haskell. –

+0

@ErikAllik Я просто показывал один из механизмов взаимодействия потоков друг с другом. Я был бы рад прочитать ваш ответ. – Sibi

8

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

В этом случае вы можете использовать atom для хранения значения. Изменения, внесенные в atom, являются атомными и будут сделаны оптимистично с помощью STM от clojure. Атомы являются одним из ссылочных типов clojure. Атом по существу является ссылкой на значение, которое может изменяться со временем контролируемым образом через функции мутации атома.

Для получения дополнительной информации об атомах и других типах ссылок см. Clojure docs.

0

Я также рассмотрю часть Haskell.

Во-первых, я хочу, чтобы очистить что-то:

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

Это не так точно. Мы создаем новые «переменные» в FP, когда они нам нужны, а не когда нам нужно мутировать существующие. Мы даже не думаем о мутации, когда делаем то, что вы описываете; мы можем просто подумать, что мы создаем новое значение, подобное тому, которое у нас есть.

То, что вы описываете с помощью потоков, немного отличается. Вы действительно ищете побочный эффект (увеличение счетчика). Haskell, будучи чистым, не просто позволит вам бросить побочные эффекты, не будучи очень явным. Поэтому в этом случае вам нужно будет обратиться к ссылочным типам/изменяемым ячейкам.Самый простой из них называется IORef, и он очень похож на переменную в этом смысле; вы можете присвоить значение, прочитать текущее значение и так далее.

Итак, как вы можете видеть, когда вы ищете такие вещи, у вас действительно есть только одна копия счетчика.

Вышесказанное является сущностью моего ответа, но вы конкретно задали вопросы о потоках, чтобы я ответил на это как-хорошо.
IORef s на самом деле не являются потокобезопасными. Итак, есть MVar s, как было предложено. Они не похожи на обычные переменные, но они близки, и они выполняют свою работу элегантно. Вообще и свободно говоря: они абстрагируют переменные и блокируют. Я думаю, вы могли бы найти TVar s проще, хотя. Они ведут себя как IORef/переменные, только в отличие от обоих, они потокобезопасны; вы можете составить их операции в одну операцию, и любая операция, выполняемая с ними, выполняется атомарно (STM).

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

0

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

Как правило, вы можете сделать то же самое с помощью рекурсии, так, например, если у вас есть функция ниже:

somefun()-> 
    somefun(0). 
somefun (X) -> 
    perform_http_request(), 
    if(something!=quit) 
    somefun(X+1) 
end function. 

generate_thread(0, Accumulator) -> 
     Accumulator; 
generate_thread(X, Accumulator) -> 
     Y = somefun(), 
     NewAccumulator = add_to_accumulator(Y), 
     generate_thread(X-1, NewAccumulator). 

Я только что ввели это в спешке, и это очень общее объяснение (не будет способный использовать этот код напрямую), но думаю, что вы можете обнаружить, что вы действительно не изменяете здесь ... Функция завершится, когда все потоки завершат обработку, теперь фактическая синхронизация потоков вы можете сделать в зависимости от языка ваш выбор и разные языки имеют разные способы обработки параллелизма и «потоков». Я бы предложил вам взглянуть на Erlang, если вы находитесь в параллелизме, поскольку у него действительно хорошая модель параллелизма imo.

В любом случае, в конце вы можете просто суммировать все значения в аккумуляторе, которые возвращаются и отображаться на них, также взглянуть на функции foldl и foldr.