我如何用Haskell模拟Go的通道?

huangapple go评论108阅读模式
英文:

How can I emulate Go's channels with Haskell?

问题

我最近开始阅读关于Go编程语言的内容,我发现通道变量是一个非常吸引人的概念。在Haskell中是否有可能模拟相同的概念?也许可以有一个数据类型Channel a和一个Monad结构来实现可变状态和类似关键字go的函数。

我在并发编程方面并不是很擅长,像Haskell中这样简单的通道传递机制确实会让我的生活变得更轻松。

EDIT

有人要求我澄清我对于想要将Go的模式转换为Haskell的具体内容。所以Go有一种通道变量,它们是一等公民,可以通过函数传递和返回。我可以读写这些通道,因此可以在可以并发运行的例程之间轻松通信。Go还有一个go关键字,根据语言规范,它启动一个函数的并发执行作为一个独立的线程,并继续执行代码而不等待。

我感兴趣的确切模式是这样的(Go的语法很奇怪-变量是通过varName varType声明而不是通常的倒置方式-但我认为它是可读的):

func generateStep(ch chan int) {
      //ch是类型为chan int的变量,它是一个通信整数的通道
      for {
          ch <- randomInteger() //只是将随机整数发送到通道中
      }

func filter(input, output chan int) {
      state int
      for {
          step <- input  //从输入通道读取一个整数
          newstate := update(state, step) //使用某个更新函数更新变量
          if criteria(newstate, state) {
             state = newstate //如果新状态满足某些条件,则接受更新
          } 
          output <- state    //将其传递到输出通道
      } 
}

func main() {
    intChan := make(chan int) 
    mcChan  := make(chan int) 
    go generateStep(intChan)     //并发执行通道
    go filter(intChan, mcChan)
    for i:=0; i<numSteps; i++  {
        x <- mcChan        //从过滤通道获取值
        accumulateStats(x)  //计算一些统计数据
    } 
    printStatisticsAbout(x)
}

我主要的兴趣是进行蒙特卡洛模拟,通过尝试修改系统的当前状态并接受满足某些条件的修改来顺序生成配置。

事实上,使用这些通道的东西,我可以编写一个非常简单、可读性强且小型的蒙特卡洛模拟,可以在我的多核处理器上并行运行,这让我印象深刻。

问题是Go有一些限制(特别是在我习惯的Haskell中缺乏多态性),而且我真的很喜欢Haskell,不想放弃它。所以问题是是否有一种简单的方式来使用类似上面代码的机制在Haskell中进行并发模拟。

EDIT(2, 上下文):
我对计算机科学不太了解,特别是并发方面。我只是一个在与计算机科学完全无关的学科中解决简单问题的人。我只是觉得Haskell的工作方式很有趣,喜欢用它来做一些小事情。

我从未听说过独立的π演算或CSP通道。如果问题看起来不合适,那可能是因为我对这个问题的巨大无知。

你是对的,我应该更具体地说明我想要在Haskell中复制哪种Go模式,我会尝试编辑问题以更具体。但是不要期望深入的理论问题。问题就是,从我读过和编写过的少量内容来看,Go似乎有一种很好的并发方式(对我来说,这只意味着让我的所有核心都在进行数值计算),如果我可以在Haskell中使用类似的语法,我会很高兴。

英文:

I recently started reading about the Go programming language and I found the channel variables a very appealing concept. Is it possible to emulate the same concept in Haskell? Maybe to have a data type Channel a and a monad structure to enable mutable state and functions that work like the keyword go.

I'm not very good in concurrent programming and a simple channel passing mechanism like this in Haskell would really make my life easier.

EDIT

People asked me to clarify what kind of Go's patterns I was interested in translating to Haskell. So Go has channel variables that are first class and can be passed around and returned by functions. I can read and write to these channels, and so communicate easily between routines that can run concurrently. Go also has a go keyword, that according to the language spec initiates the execution of a function concurrently as an independent thread and continues to execute the code without waiting.

The exact pattern I'm interested in is something like this (Go's syntax is weird - variables are declared by varName varType instead of the usual inverted way - but I think it is readable):

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

func filter(input, output chan int) {
      state int
      for {
          step &lt;- 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 &lt;- 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&lt;numSteps; i++  {
        x &lt;- mcChan        // get values from the filtered channel
        accumulateStats(x)  // calculate some statistics
    } 
    printStatisticsAbout(x)
}

My primary interest is to do Monte Carlo simulations, in which I generate configurations sequentially by trying to modify the current state of the system and accepting the modification if it satisfies some criteria.

The fact the using those channel stuff I could write a very simple, readable and small Monte Carlo simulation that would run in parallel in my multicore processor really impressed me.

The problem is that Go have some limitations (specially, it lacks polymorphism in the way I'm accustomed to in Haskell), and besides that, I really like Haskell and don't wanna trade it away. So the question is if there's some way to use some mechanics that looks like the code above to do a concurrent simulation in Haskell easily.

EDIT(2, context):
I'm not learned in Computer Science, specially in concurrency. I'm just a guy who creates simple programs to solve simple problems in my daily research routine in a discipline not at all related to CS. I just find the way Haskell works interesting and like to use it to do my little chores.

I never heard about alone pi-calculus or CSP channels. Sorry if the question seems ill posed, it's probably my huge-ignorance-about-the-matter's fault.

You are right, I should be more specific about what pattern in Go I'd like to replicate in Haskell, and I'll try to edit the question to be more specific. But don't expect profound theoretical questions. The thing is just that, from the few stuff I read and coded, it seems Go have a neat way to do concurrency (and in my case this just means that my job of making all my cores humming with numerical calculations is easier), and if I could use a similar syntax in Haskell I'd be glad.

答案1

得分: 40

我认为你正在寻找的是来自Base的Control.Concurrent.Chan。除了明显的Haskell化之外,我没有发现它与go的chan有任何不同之处。通道并不是go特有的东西,你可以查看关于它的维基页面

通道是更一般概念中的一部分,称为通信顺序进程(CSP),如果你想以CSP的方式在Haskell中进行编程,你可能想看看Communicating Haskell Processes (CHP)包。

CHP只是在Haskell中进行并发的一种方式,你可以查看Haskellwiki并发页面获取更多信息。我认为你的用例可能最好使用Data Parrallel Haskell编写,但目前这还是一个正在进行中的工作,所以你可能想暂时使用其他东西。

英文:

I think what you are looking for is Control.Concurrent.Chan from Base. I haven't found it to be any different from go's chans other then the obvious haskellifications. Channels aren't something that is special to go, have a look at the wiki page about it.

Channels are part of a more general concept called communicating sequential processes (CSP), and if you want to do programming in the style of CSP in Haskell you might want to take a look at the Communicating Haskell Processes (CHP) package.

CHP is only one way of doing concurrency in Haskell, take a look at the Haskellwiki concurrency page for more information. I think your use case might be best written using Data Parrallel Haskell, however that is currently a work in progress, so you might want to use something else for now.

答案2

得分: 2

扩展HaskellElephant的答案,Control.Concurrent.Chan是通道的方式,Control.Concurrent的forkIO可以模拟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 "无法从关闭的通道读取!"
        else readChan $ chan ch

(=>) :: a -> GoChan a -> IO ()
v => ch = do
    cl <- readMVar $ closed ch
    if cl
        then error "无法向关闭的通道写入!"
        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

(警告:未经测试的代码)

英文:

Extending HaskellElephant's answer, Control.Concurrent.Chan is the way to go for channels and Control.Concurrent's forkIO can emulate the go keyword. To make the syntax a bit more similar to Go, this set of aliases can be used:

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 () -&gt; IO ThreadId
go = forkIO

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

get :: GoChan a -&gt; IO a
get ch = do
    cl &lt;- readMVar $ closed ch
    if cl
        then error &quot;Can&#39;t read from closed channel!&quot;
        else readChan $ chan ch

(=-&gt;) :: a -&gt; GoChan a -&gt; IO ()
v =-&gt; ch = do
    cl &lt;- readMVar $ closed ch
    if cl
        then error &quot;Can&#39;t write to closed channel!&quot;
        else writeChan (chan ch) v

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

This can be used like so:

import Control.Monad

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

process :: GoChan Int -&gt; IO ()
process c = forRange c print

main :: IO ()
main = do
    c &lt;- make
    go $ generate c
    process c

(Warning: untested code)

huangapple
  • 本文由 发表于 2010年12月24日 04:52:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/4522387.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定