Haskell:如何使用GTK和函数图绘制一些像素(副作用)



我正在尝试学习gtk2hs,一个允许Haskell程序使用窗口,菜单,工具栏和图形的API。

我想画曼德布洛特

集合,所以我开始写这些东西,但我被困在最后,即当我不得不使用副作用来绘制曼德布洛特集合的每个点时。

数据是:我有一个 300px * 200px 的画布(绘图区域(和一个函数mandelbrot :: Float -> Float -> Bool,如果点在曼德布洛特集合中,其输出为真,否则为假。

要实现的工作是:对于每个像素(宽度:0到300,高度:0到200(,将坐标转换为[-2..2]*[-2..2]的范围,调用曼德布洛特函数,如果结果为真,则调用绘制点的包Cairo的函数。(该函数C.rectangle a b 1 1

我的尝试:

example :: Double -> Double -> C.Render ()
example width height = do
  setSourceRGB 0 0 0
  setLineWidth 1
  let 
    affiche a b = do 
      if (mandelbrot a b) then 
        C.rectangle a b 1 1
        return()
      return ()
    colonnes = [0..299]
    lignes = [0..199]
  in 
    map (t -> t/300*4-2) colonnes
    map (t -> t/200*4-2) lignes
    map affiche (zip (colonnes,lignes))
    stroke            -- it displays the changes on the screen

它会触发错误:- 在第二return

Error: Parse error: return

谢谢

编辑:非常感谢您的回答。我大大改进了我的程序,但我仍然有错误。

这是最新版本:

affiche :: Double -> Double -> Render()
affiche a b = when (mandelbrot a b) $ C.rectangle a b 1 1
colonnes = [ t/300.0*4.0-2.0 | t<-[0.0..299.0] ]
lignes = [ t/200.0*4.0-2.0 | t<-[0.0..199.0] ]

example :: Double -> Double -> C.Render ()
example width height = do
    setSourceRGB 0 0 0
    setLineWidth 1
    mapM_ ( (a, b) -> affiche a b) (zip (colonnes,lignes))
    stroke

错误是:

(所有 2 个都与"mapM_"行(:

*   Couldn't match type ‘[(a0, b0)]’ with ‘(Double, Double)’
  Expected type: [b0] -> (Double, Double)
    Actual type: [b0] -> [(a0, b0)]
  In the second argument of ‘mapM_’, namely
    ‘(zip (colonnes, lignes))’
  In a stmt of a 'do' block:
    mapM_ ( (a, b) -> affiche a b) (zip (colonnes, lignes))

*  Couldn't match expected type ‘[a0]’
              with actual type ‘([Double], [Double])’
  In the first argument of ‘zip’, namely ‘(colonnes, lignes)’
  In the second argument of ‘mapM_’, namely
    ‘(zip (colonnes, lignes))’
  In a stmt of a 'do' block:
    mapM_ ( (a, b) -> affiche a b) (zip (colonnes, lignes))

还有一个问题:请确认我,如果一个函数返回类型"Render((",那么它中的所有语句都应该返回这种类型的值。谢谢

Haskell中的if结构与其他语言不同:它实际上等同于类C语言中? :的三元条件运算符。这意味着,如果你使用if ... then,你还必须有一个else分支。

原因是:Haskell不是强制性的;你不会写出应该做什么,而是写出结果应该是什么。在命令式语言中,你什么都不做,但在Haskell中,你总是需要指定一些结果。

现在,一元do块当然基本上是一种命令式嵌入式语言。在这里,您可以指定本地结果应该是无操作操作,以实现此时不执行任何操作:

    affiche a b = do 
      if mandelbrot a b then 
        C.rectangle a b 1 1
        return ()   -- note: `return` isn't needed here
       else
        return ()
      return ()  -- nor here

一个更简短的编写方法是使用 when 组合器,它基本上是 if-then-else 在 else-branch 中带有return ()

    affiche a b = when (mandelbrot a b) $ C.rectangle a b 1 1

您的代码还有另一个问题:

  in 
    map (t -> t/300*4-2) colonnes      -- number list
    map (t -> t/200*4-2) lignes        -- number list
    map affiche (zip (colonnes,lignes)) -- list of `Render ()` actions
    stroke                              -- `Render ()` action

在那里,你只是写出几个完全不同的表达方式。你不能这么做!哈斯克尔应该如何处理这些表达方式

好吧,显然你想执行一系列操作。因此,您需要再次do

  in do
    ...

但是你从map (t -> t/300*4-2) colonnes那里得到的这些列表根本不是操作。你不能执行它们,只能评估它们。在这种情况下,显然您希望colonnes是将该函数映射到列表[0..299]的结果。好吧,那你为什么不马上指定呢?

    colonnes = map (t -> t/300*4-2) [0..299]
    lignes = map (t -> t/200*4-2) [0..199]

或者,为什么不作为列表理解

    colonnes = [ t/300*4-2 | t<-[0..299] ]
    lignes = [ t/200*4-2) t<-[0..199] ]

最后,您需要在列表上映射affiche。这实际上是一个"一元行动图"。此功能是 mapM_ ,而不是map

    colonnes = [ t/300*4-2 | t<-[0..299] ]
    lignes = [ t/200*4-2) t<-[0..199] ]
  in
    mapM_ affiche $ zip colonnes lignes
    stroke

几乎在那里,但不完全是。如果affiche的签名需要两个数字的元组(你从zip那里得到这样的元组(,那么这将起作用。然而,affiche a b = do意味着函数是柯里化的(这是Haskell的惯例,特别是对于zip!不过,您可以轻松地撤消它

    mapM_ (uncurry affiche) $ zip colonnes lignes

但在这种特定情况下,我实际上建议改为定义

    affiche :: (Float,Float) -> C.Render ()
    affiche (a,b) = when (mandelbrot a b) $ C.rectangle a b 1 1

因为ab真正属于一起,形成了一个单一的坐标规范。

还有一个问题:你在这里使用了Float数字。井。。。在哈斯克尔,这样做真的没有意义。无论如何,rectangle需要Double作为参数,所以要么切换affichemandelbrot也采用Double,要么在传递给rectangle之前转换ab

    affiche :: (Float,Float) -> C.Render ()
    affiche (a,b) = when (mandelbrot a b)
                  $ C.rectangle (realToFrac a) (realToFrac b) 1 1

哦,还有一件事:我认为zip在这里做不对的事情。但。。。自己想办法...

最新更新