Как я могу эмулировать каналы Go с помощью Haskell?

Недавно я начал читать о языке программирования Go, и мне показалось, что переменные канала очень привлекательны. Можно ли подражать той же концепции в Haskell? Может быть, иметь тип данных Channel a и структуру монад, чтобы включить изменяемое состояние и функции, которые работают как ключевое слово go.

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

ИЗМЕНИТЬ

Люди просили меня уточнить, какие шаблоны Go мне интересно перевести на Haskell. Итак, в Go есть канальные переменные первого класса, которые могут передаваться и возвращаться функциями. Я могу читать и писать в эти каналы, и поэтому легко обмениваюсь данными между подпрограммами, которые могут выполняться одновременно. Go также имеет ключевое слово go, которое в соответствии со спецификацией языка инициирует выполнение функции одновременно как независимый поток и продолжает выполнение кода без ожидания.

Точный шаблон, который меня интересует, выглядит примерно так (синтаксис Go странный - переменные объявляются с помощью varName varType вместо обычного перевернутого способа - но я думаю, что он читаем):

func generateStep(ch chan int) {
      //ch is a variable of type chan int, which is a channel that comunicate integers
      for {
          ch <- randomInteger() //just sends random integers in the channel 
      }

func filter(input, output chan int) {
      state int
      for {
          step <- input  //reads an int from the input channel
          newstate := update(state, step) //update the variable with some update function
          if criteria(newstate, state) {
             state = newstate // if the newstate pass some criteria, accept the update
          } 
          output <- state    //pass it to the output channel
      } 
}

func main() {
    intChan := make(chan int) 
    mcChan  := make(chan int) 
    go generateStep(intChan)     // execute the channels concurrently
    go filter(intChan, mcChan)
    for i:=0; i<numSteps; i++  {
        x <- mcChan        // get values from the filtered channel
        accumulateStats(x)  // calculate some statistics
    } 
    printStatisticsAbout(x)
}

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

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

Проблема в том, что у Go есть некоторые ограничения (особенно, в нем отсутствует полиморфизм, как я привык в Haskell), и, кроме того, мне очень нравится Haskell, и я не хочу отказываться от него. Итак, вопрос в том, есть ли способ использовать какую-то механику, похожую на приведенный выше код, чтобы легко выполнять параллельное моделирование в Haskell.

EDIT (2, context): Я не изучал компьютерные науки, особенно параллелизм. Я просто парень, который создает простые программы для решения простых задач в моей повседневной исследовательской работе в дисциплине, совершенно не связанной с CS. Мне просто интересно то, как работает Haskell, и я люблю использовать его для выполнения своих небольших задач.

Я никогда не слышал только о пи-исчислении или каналах CSP. Извините, если вопрос кажется некорректным, возможно, это моя вина из-за огромного незнания этого вопроса.

Вы правы, я должен быть более конкретным в отношении того, какой шаблон в Go я хотел бы воспроизвести в Haskell, и я постараюсь отредактировать вопрос, чтобы он был более конкретным. Но не ждите глубоких теоретических вопросов. Дело в том, что из немногих вещей, которые я прочитал и закодировал, кажется, что у Go есть изящный способ реализовать параллелизм (и в моем случае это просто означает, что моя работа по заставлению всех моих ядер гудеть числовыми вычислениями проще), и если бы я мог использовать аналогичный синтаксис в Haskell, я был бы рад.


person Rafael S. Calsaverini    schedule 23.12.2010    source источник
comment
Я уверен, что вы это уже прошли, но я только что написал учебник по переводу некоторых примитивов параллелизма Go в Haskell.   -  person Daniel Buckmaster    schedule 28.03.2015


Ответы (2)


Я думаю, что вы ищете Control.Concurrent.Chan из базы. Я не обнаружил, что он чем-то отличается от чанов го, кроме очевидных хаскеллификаций. Каналы не являются чем-то особенным, посмотрите на страницу wiki об этом .

Каналы являются частью более общей концепции, называемой взаимодействия последовательных процессов (CSP), и если вы хотите программировать в стиле CSP в Haskell, возможно, вы захотите взглянуть на Общение между процессами Haskell (CHP) пакет.

CHP - это только один из способов реализации параллелизма в Haskell, взгляните на страницу параллелизма Haskellwiki для дополнительной информации. Я думаю, что ваш вариант использования может быть лучше всего написан с использованием Data Parrallel Haskell, однако в настоящее время это работа в процессе, так что вы можете использовать что-нибудь еще.

person HaskellElephant    schedule 23.12.2010
comment
Нашел действительно хорошую статью об этом. Спасибо. mult.ifario.us/p/ - person Rafael S. Calsaverini; 24.12.2010
comment
Просто примечание: Control.Concurrent.Chan реализует неограниченные каналы, тогда как Go имеет ограниченные каналы. Такие каналы доступны в пакете BoundedChan - person edofic; 17.10.2014
comment
Кажется, что ни Control.Concurrent.Chan, ни Control.Concurrent.BoundedChan не предлагают способа выбрать один канал из нескольких альтернатив. Вспомните alts! в Clojure: clojuredocs.org/clojure.core.async/alts! - person ath; 06.07.2021

Расширяя ответ HaskellElephant, Control.Concurrent.Chan - это способ перейти к каналам, а forkIO Control.Concurrent может имитировать ключевое слово go. Чтобы сделать синтаксис немного более похожим на Go, можно использовать этот набор псевдонимов:

import Control.Concurrent (forkIO)
import Control.Concurrent.Chan (newChan, readChan, writeChan)
import Control.Concurrent.MVar (newMVar, swapMVar, readMVar)

data GoChan a = GoChan { chan :: Chan a, closed :: MVar Bool }

go :: IO () -> IO ThreadId
go = forkIO

make :: IO (GoChan a)
make = do
    ch <- newChan
    cl <- newMVar False
    return $ GoChan ch cl

get :: GoChan a -> IO a
get ch = do
    cl <- readMVar $ closed ch
    if cl
        then error "Can't read from closed channel!"
        else readChan $ chan ch

(=->) :: a -> GoChan a -> IO ()
v =-> ch = do
    cl <- readMVar $ closed ch
    if cl
        then error "Can't write to closed channel!"
        else writeChan (chan ch) v

forRange :: GoChan a -> (a -> IO b) -> IO [b]
forRange ch func = fmap reverse $ range_ ch func []
    where range_ ch func acc = do
        cl <- readMVar $ closed ch
        if cl
            then return ()
            else do
                v <- get ch
                func v
                range_ ch func $ v : acc
close :: GoChan a -> IO ()
close ch = do
    swapMVar (closed ch) True
    return ()

Это можно использовать так:

import Control.Monad

generate :: GoChan Int -> IO ()
generate c = do
    forM [1..100] (=-> c)
    close c

process :: GoChan Int -> IO ()
process c = forRange c print

main :: IO ()
main = do
    c <- make
    go $ generate c
    process c

(Предупреждение: непроверенный код)

person Tuomas Laakkonen    schedule 09.07.2016
comment
попробовал ваш код; были некоторые проблемы. Во-первых, мне пришлось вставить threadDelay между производителем и потребителем, а во-вторых, для вашего закрытого MVar может быть установлено значение True до того, как потребитель завершит чтение, что приведет к неполному чтению последовательности. Мне больше нравится эта версия, где я кодирую конец потока как Nothing: gist.github.com / tallpeak / f832af9ed92395de3b60fce6a64fd992 - person Aaron West; 24.10.2018