haskell : 如何使用 gtk 和函数映射绘制一些像素(副作用)
haskell : how to draw some pixels (side effects) using gtk and the function map
我正在尝试学习 gtk2hs,一个 API 允许 haskell 程序使用 windows、菜单、工具栏和图表。
想画Mandelbrot集,所以开始写东西,但是卡在最后,就是要用side effects来画Mandelbrot集的每个点。
数据是:我有一个 300px * 200px 的 canvas(绘图区域)和一个函数 mandelbrot :: Float -> Float -> Bool
,如果该点在 mandelbrot 集中,其输出为真,并且否则为假。
要实现的工作是:对于每个像素(宽度:0 到 300,高度:0 到 200),将坐标转换到 [-2..2]*[-2..2] 范围内,调用mandelbrot函数,如果结果为真,则调用包Cairo which draws the point. (The function is 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))
我还有一个问题:请确认如果一个函数 return 是 "Render()" 类型,那么它里面的所有语句都应该 return 这个类型的值。
谢谢
Haskell 中的 if
结构与其他语言不同:它实际上等同于三元 条件运算符 即 ? :
在类 C 语言中。这意味着,如果您使用 if ... then
,您 必须 也有一个 else
分支。
原因:Haskell不是必须的;你不会写出应该做什么,而是结果应该是什么。在命令式语言中,你可以什么都不做,但在Haskell中你总是需要指定一些结果。
现在,monadic do
块当然基本上是一种命令式嵌入式语言。在这里,您 可以 只指定本地结果应该是 no-op 操作,以实现此时什么都不做:
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 分支中的 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
在那里,您只需写出几个完全不同类型的表达式。你不能那样做!使用这些表达式,Haskell 应该做什么?
好吧,显然您想执行一系列操作。因此你又需要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
列表。这实际上是一个“monadic action map”。这个函数是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
意味着函数是 curried(在 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
因为a
和b
确实属于同一类,形成了一个单一的坐标规范。
还有一个问题:您在这里使用了 Float
个数字。嗯... it doesn't really make sense to do that, in Haskell. At any rate, rectangle
需要 Double
作为参数,所以要么将 affiche
和 mandelbrot
都切换为也采用 Double
,要么将 a
和b
在传递给 rectangle
之前:
affiche :: (Float,Float) -> C.Render ()
affiche (a,b) = when (mandelbrot a b)
$ C.rectangle (realToFrac a) (realToFrac b) 1 1
哦,还有一件事:我认为 zip
在这里做的事情不对。但是...自己想办法...
我正在尝试学习 gtk2hs,一个 API 允许 haskell 程序使用 windows、菜单、工具栏和图表。
想画Mandelbrot集,所以开始写东西,但是卡在最后,就是要用side effects来画Mandelbrot集的每个点。
数据是:我有一个 300px * 200px 的 canvas(绘图区域)和一个函数 mandelbrot :: Float -> Float -> Bool
,如果该点在 mandelbrot 集中,其输出为真,并且否则为假。
要实现的工作是:对于每个像素(宽度:0 到 300,高度:0 到 200),将坐标转换到 [-2..2]*[-2..2] 范围内,调用mandelbrot函数,如果结果为真,则调用包Cairo which draws the point. (The function is 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))
我还有一个问题:请确认如果一个函数 return 是 "Render()" 类型,那么它里面的所有语句都应该 return 这个类型的值。 谢谢
Haskell 中的 if
结构与其他语言不同:它实际上等同于三元 条件运算符 即 ? :
在类 C 语言中。这意味着,如果您使用 if ... then
,您 必须 也有一个 else
分支。
原因:Haskell不是必须的;你不会写出应该做什么,而是结果应该是什么。在命令式语言中,你可以什么都不做,但在Haskell中你总是需要指定一些结果。
现在,monadic do
块当然基本上是一种命令式嵌入式语言。在这里,您 可以 只指定本地结果应该是 no-op 操作,以实现此时什么都不做:
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 分支中的 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
在那里,您只需写出几个完全不同类型的表达式。你不能那样做!使用这些表达式,Haskell 应该做什么?
好吧,显然您想执行一系列操作。因此你又需要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
列表。这实际上是一个“monadic action map”。这个函数是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
意味着函数是 curried(在 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
因为a
和b
确实属于同一类,形成了一个单一的坐标规范。
还有一个问题:您在这里使用了 Float
个数字。嗯... it doesn't really make sense to do that, in Haskell. At any rate, rectangle
需要 Double
作为参数,所以要么将 affiche
和 mandelbrot
都切换为也采用 Double
,要么将 a
和b
在传递给 rectangle
之前:
affiche :: (Float,Float) -> C.Render ()
affiche (a,b) = when (mandelbrot a b)
$ C.rectangle (realToFrac a) (realToFrac b) 1 1
哦,还有一件事:我认为 zip
在这里做的事情不对。但是...自己想办法...