Posted on: 28/08/2016
https://github.com/chrissound/DrawStuffWithHaskellArticle
Lets go!
Create a cabal sandbox: cabal sandbox init
Create the necessary cabal files. You could copy the below files or run through cabal init
.
drawStuffWithHaskell.cabal
-- Initial img.cabal generated by cabal init. For further documentation,
-- see http://haskell.org/cabal/users-guide/
name: drawStuffWithHaskell
version: 0.1.0.0
-- synopsis:
-- description:
license: MIT
license-file: LICENSE
author: Chris Stryczynski
maintainer: chris@trycatchchris.co.uk
-- copyright:
-- category:
build-type: Simple
-- extra-source-files:
cabal-version: >=1.10
executable img
main-is: Main.hs
-- other-modules:
-- other-extensions:
build-depends: base >=4.8
, gloss
, bytestring
, containers
, random
-- hs-source-dirs:
default-language: Haskell2010
Draw a circle
Main.hs
module Main(main) where
import Graphics.Gloss
window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)
background :: Color
background = white
drawing :: Picture
drawing = circle 80
main :: IO ()
main = display window background drawing

Draw four circles
But how? With some googling about we find pictures
. Wtf is pictures? Open the cabal REPL cabl repl
:
import Graphics.Gloss
:t pictures
Output:
pictures :: [Picture] -> Picture
It takes an array (list?) of elements of type Picture and returns a single Picture.
Main.hs
module Main(main) where
import Graphics.Gloss
window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)
background :: Color
background = white
drawing :: Picture
drawing = pictures
[
circle 10
, circle 20
, circle 30
, circle 40
, circle 50
]
main :: IO ()
main = display window background drawing

Functionify
Ooooo.
For some reason the first parameter to circle
must be a Float
(instead of Int
).
module Main(main) where
import Graphics.Gloss
window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)
background :: Color
background = white
getCircle :: Float -> Picture
getCircle size = circle size
get5Circles :: [Picture]
get5Circles = map getCircle [10,20..50]
drawing :: Picture
drawing = pictures get5Circles
main :: IO ()
main = display window background drawing
Functionify
Main.hs
module Main(main) where
import Graphics.Gloss
window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)
background :: Color
background = white
getCircle :: Float -> Picture
getCircle size = circle size
getCircles :: Int -> [Picture]
getCircles count = map getCircle (take count [10,20..])
drawing :: Picture
drawing = pictures (getCircles 5)
main :: IO ()
main = display window background drawing
Type annotations are optional. Yes holy shit!
Main.hs
module Main(main) where
import Graphics.Gloss
window = InWindow "Nice Window" (200, 200) (10, 10)
background = white
getCircle size = circle size
getCircles count = map getCircle (take count [10,20..])
drawing = pictures (getCircles 5)
main = display window background drawing
Lets go deeper.
I’ve brought back the type annotations for readability. Let’s create a new data type and lets create a new function getAngle
that returns an angle between two points of the outline of the circle and the center. getAngle
takes two parameters. Notice the slightly different type signature. Basically Haskell function’s return is of the last type specified in the type signature.
So in other words getAngle
is a function that takes two parameters both of type Integer
and returns a type of Float
.
If you’re interested in the logic behind this you can read up about ‘Haskell function currying’.
Main.hs
module Main(main) where
import Graphics.Gloss
data Point = Point Float Float deriving Show
window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)
background :: Color
background = white
getCircle :: Float -> Picture
getCircle size = circle size
getCircles :: Int -> [Picture]
getCircles count = map getCircle (take count [10,20..])
getAngle :: Integer -> Integer -> Float
getAngle pointIndex pointCount = 2 * pi * (fromInteger pointIndex / fromInteger pointCount)
drawing :: Picture
drawing = pictures (getCircles 5)
main :: IO ()
main = display window background drawing
Draw some more circle at particular points
A new thing we’re using here is a where statement, which as far as I can tell is just syntactic sugar at this point. However a rather cool thing is we can reference other values within the expression without them being defined previously. This rather amazing feature is thanks to Haskell`s lazy loading. Here is an example:
x = a+b where
a = c+d
b = c*3
c = 5
d = 4
The above will have define x as the value 24 (c+d)+(c3) = (5 + 4)+(53) = 24
We also need the help of fromIntegral
to help us convert between numeric types. We’re also using an anonymous function with the map.
And we’re also using Translate
which is a data constructor, basically a function that constructs a data type, hence the reason it’s capitalized (it’s a requirement of Haskell syntax).
Oh and we’re also using pattern matching with the PointCoordinate
data type, (to get the x and y values).
Main.hs
module Main(main) where
import Graphics.Gloss
data PointCoordinate = PointCoordinate Float Float deriving Show
window :: Display
window = InWindow "Nice Window" (200, 200) (10, 10)
background :: Color
background = white
getCircle :: Float -> Picture
getCircle size = circle size
getAngle :: Integer -> Integer -> Float
getAngle pointIndex pointCount = 2 * pi * (fromInteger pointIndex / fromInteger pointCount)
getCirclePoint :: Float -> Float -> PointCoordinate
getCirclePoint radius angle = PointCoordinate
(radius * sin(angle))
(radius * cos(angle))
getTranslatedCircle :: Integer -> Integer -> Picture
getTranslatedCircle index indexCount = Translate x y (getCircle unitLength)
where (PointCoordinate x y) = getCirclePoint radius (getAngle index indexCount)
radius = unitLength + (unitLength * ((fromIntegral index) / (fromIntegral indexCount)))
unitLength = 30
drawing :: Picture
drawing = pictures (map (\pointIndex -> getTranslatedCircle pointIndex pointCount) [1..pointCount])
where pointCount = 10
main :: IO ()
main = display window background drawing

No comments, yet!