Ad-hoc Polymorphism vs. Data Constructors in Haskell - haskell

Defining different data constructors for a type, and then defining functions using pattern matching over them resembles ad-hoc polymorphism in other languages a lot:
data Shape = Circle Float | Rectangle Float Float
area :: Shape -> Float
area (Circle r) = pi * r^2
area (Rectangle a b) = a*b
Using type classes you could do
class Shape s where
area :: s -> Float
data Circle = Circle Float
instance Shape Circle where
area (Circle r) = pi * r^2
data Rectangle = Rectangle Float Float
instance Shape Rectangle where
area (Rectangle a b) = a*b
One advantage of the second that I see is that it can be extended without touching existing declarations, in particular it may be your only option. Are there other reasons to prefer one over the other?

To make the 2nd approach work, you have to know the type s at compile time while with the 1st approach you can match runtime values
data Shape = Circle Float | Rectangle Float Float
area :: Shape -> Float
area (Circle r) = pi * r^2
area (Rectangle a b) = a*b
fromString :: String -> [Float] -> Shape
fromString "circle" (r:_) = Circle r
fromString "rectangle" (a:b:_) = Rectangle a b
areaFromString :: String -> [Float] -> Float
areaFromString s params = area $ fromString s params
Difference between Type Class and Algebraic data types
Scala: difference between a typeclass and an ADT?
Type classes vs algebraic data types?

To compare these approaches, it helps to see more than the data type declarations.
Suppose we want to write a Haskell program to parse a shape specification and display it as an ASCII diagram on the screen.
Solution #1
One method is to define our Shape as an ADT:
type Pos = (Float, Float)
type Scalar = Float
data Shape
= Circle Pos Scalar
| Rectangle Pos Scalar Scalar
deriving (Show)
To parse the specification, we'll use Megaparsec. This will require a Parser Shape:
shape :: Parser Shape
shape = Circle <$ char 'C' <*> pos <*> scalar
<|> Rectangle <$ char 'R' <*> pos <*> scalar <* char ',' <*> scalar
plus some additional pos and scalar parsers (see below).
To render the shape, we'll first calculate its bounding box:
data Box = Box Pos Pos deriving (Show)
bbox :: Shape -> Box
bbox (Circle (x,y) r) = Box (x-r,y-r) (x+r,y+r)
bbox (Rectangle (x,y) w h) = Box (x-w/2,y-h/2) (x+w/2,y+h/2)
and then write it to a raster over that bounding box:
type Raster = [[Char]]
render :: Box -> (Int, Int) -> Shape -> Raster
render (Box (x1,y1) (x2,y2)) (cx,cy) s
= [[ if inShape (x,y) s then 'X' else ' '
| x <- [x1',x1'+delta..x2']] | y <- [y1',y1'-delta*caspect..y2']]
where --- ugly computations of x1', etc. go here
This relies on a function that can determine if a raster point is inside the shape or not:
inShape :: Pos -> Shape -> Bool
inShape (x,y) (Circle (cx,cy) r) = sq (x-cx) + sq (y-cy) <= sq r
where sq a = a*a
inShape (x,y) (Rectangle (cx,cy) w h)
= cx-w/2 <= x && x <= cx+w/2
&& cy-h/2 <= y && y <= cy+h/2
After adding some driver/main functions and filling in the details, the full program is:
{-# OPTIONS_GHC -Wall #-}
import Data.Void
import Text.Megaparsec hiding (Pos)
import Text.Megaparsec.Char
type Pos = (Float, Float)
type Scalar = Float
data Shape
= Circle Pos Scalar
| Rectangle Pos Scalar Scalar
deriving (Show)
type Parser = Parsec Void String
shape :: Parser Shape
shape = Circle <$ char 'C' <*> pos <*> scalar
<|> Rectangle <$ char 'R' <*> pos <*> scalar <* char ',' <*> scalar
pos :: Parser Pos
pos = (,) <$ char '(' <*> scalar <* char ',' <*> scalar <* char ')'
scalar :: Parser Scalar
scalar = do
sgn <- option 1 (-1 <$ char '-')
dig1 <- digits
dig2 <- option "" ((:) <$> char '.' <*> digits)
return $ sgn * read (dig1 ++ dig2)
where digits = some digitChar
data Box = Box Pos Pos deriving (Show)
bbox :: Shape -> Box
bbox (Circle (x,y) r) = Box (x-r,y-r) (x+r,y+r)
bbox (Rectangle (x,y) w h) = Box (x-w/2,y-h/2) (x+w/2,y+h/2)
type Raster = [[Char]]
render :: Box -> (Int, Int) -> Shape -> Raster
render (Box (x1,y1) (x2,y2)) (cx,cy) s
= [[ if inShape (x,y) s then 'X' else ' '
| x <- [x1',x1'+delta..x2']] | y <- [y1',y1'-delta*caspect..y2']]
where caspect = 2 -- font character aspect ratio
expand = 1.2 -- expansion of raster beyond bounding box
delta = max ((x2-x1) / fromIntegral cx) ((y2-y1) / fromIntegral cy / caspect)
x1' = (x1+x2)/2 - expand*cx' * delta
x2' = (x1+x2)/2 + expand*cx' * delta
y1' = (y1+y2)/2 + expand*cy' * delta * caspect
y2' = (y1+y2)/2 - expand*cy' * delta * caspect
cx' = fromIntegral cx / 2
cy' = fromIntegral cy / 2
inShape :: Pos -> Shape -> Bool
inShape (x,y) (Circle (cx,cy) r) = sq (x-cx) + sq (y-cy) <= sq r
where sq a = a*a
inShape (x,y) (Rectangle (cx,cy) w h)
= cx-w/2 <= x && x <= cx+w/2
&& cy-h/2 <= y && y <= cy+h/2
driver :: String -> IO ()
driver spec = do
putStrLn "---BEGIN---"
let res = parse (shape <* eof) "(string)" spec
case res of
Left err -> error (errorBundlePretty err)
Right s -> putStrLn $ unlines $ render (bbox s) (40,20) s
putStrLn "---END---"
main :: IO ()
main = do
driver "C(3,8)8" -- circle radius 8 centered at (3,8)
driver "R(-1,6)8,3" -- rectangle centered at (-1,6) w/ dim 8 by 3
It works fine:
λ> main
---BEGIN---
XXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXX
X
---END---
---BEGIN---
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
---END---
Suppose we want to add a shape, like an Octagon. This requires adding an Octagon constructor:
data Shape
= ...
| Octagon Pos Scalar
...
Compiler warnings direct us to add the following cases to our functions:
bbox (Octagon (x,y) w) = Box (x-w/2,y-w/2) (x+w/2,y+w/2)
inShape (x,y) (Octagon (cx,cy) w)
= cx-w/2 <= x && x <= cx+w/2
&& cy-w/2 <= y && y <= cy+w/2
&& abs (x-cx) + abs (y-cy) <= w / sqrt 2
We have to figure out on our own that the parser needs to be modified:
shape = ...
<|> Octagon <$ char 'O' <*> pos <*> scalar
This change requires 7 lines of code added in 4 locations in the source.
Suppose we now want to add an ability to create shapes that consist of unions of other shapes. This change is similar in scope and requires the addition of 7 lines of code in 4 locations.
Finally, suppose we want to add the ability to "render" to a descriptive text format. This requires defining a 5-line function in one location:
description :: Shape -> String
description (Circle c r) = "a circle centered at " ++ show c ++ " with radius " ++ show r
description (Rectangle c w h) = "a rectangle centered at " ++ show c ++ " with width " ++ show w ++ " and height " ++ show h
description (Octagon c w) = "an octagon centered at " ++ show c ++ " with width " ++ show w
description (Union s t) = "the union of " ++ description s ++ " and " ++ description t
The full program follows. It's 94 lines and 3148 characters of source, and it makes no use of advanced Haskell features other than applicative syntax for the parser.
{-# OPTIONS_GHC -Wall #-}
import Data.Void
import Text.Megaparsec hiding (Pos)
import Text.Megaparsec.Char
type Pos = (Float, Float)
type Scalar = Float
data Shape
= Circle Pos Scalar
| Rectangle Pos Scalar Scalar
| Octagon Pos Scalar
| Union Shape Shape
deriving (Show)
type Parser = Parsec Void String
shape :: Parser Shape
shape = Circle <$ char 'C' <*> pos <*> scalar
<|> Rectangle <$ char 'R' <*> pos <*> scalar <* char ',' <*> scalar
<|> Octagon <$ char 'O' <*> pos <*> scalar
<|> Union <$ char 'U' <*> shape <*> shape
pos :: Parser Pos
pos = (,) <$ char '(' <*> scalar <* char ',' <*> scalar <* char ')'
scalar :: Parser Scalar
scalar = do
sgn <- option 1 (-1 <$ char '-')
dig1 <- digits
dig2 <- option "" ((:) <$> char '.' <*> digits)
return $ sgn * read (dig1 ++ dig2)
where digits = some digitChar
data Box = Box Pos Pos deriving (Show)
bbox :: Shape -> Box
bbox (Circle (x,y) r) = Box (x-r,y-r) (x+r,y+r)
bbox (Rectangle (x,y) w h) = Box (x-w/2,y-h/2) (x+w/2,y+h/2)
bbox (Octagon (x,y) w) = Box (x-w/2,y-w/2) (x+w/2,y+w/2)
bbox (Union s t)
= let Box (x1,y1) (x2,y2) = bbox s
Box (x3,y3) (x4,y4) = bbox t
in Box (min x1 x3, min y1 y3) (max x2 x4, max y2 y4)
type Raster = [[Char]]
render :: Box -> (Int, Int) -> Shape -> Raster
render (Box (x1,y1) (x2,y2)) (cx,cy) s
= [[ if inShape (x,y) s then 'X' else ' '
| x <- [x1',x1'+delta..x2']] | y <- [y1',y1'-delta*caspect..y2']]
where caspect = 2 -- font character aspect ratio
expand = 1.2 -- expansion of raster beyond bounding box
delta = max ((x2-x1) / fromIntegral cx) ((y2-y1) / fromIntegral cy / caspect)
x1' = (x1+x2)/2 - expand*cx' * delta
x2' = (x1+x2)/2 + expand*cx' * delta
y1' = (y1+y2)/2 + expand*cy' * delta * caspect
y2' = (y1+y2)/2 - expand*cy' * delta * caspect
cx' = fromIntegral cx / 2
cy' = fromIntegral cy / 2
inShape :: Pos -> Shape -> Bool
inShape (x,y) (Circle (cx,cy) r) = sq (x-cx) + sq (y-cy) <= sq r
where sq a = a*a
inShape (x,y) (Rectangle (cx,cy) w h)
= cx-w/2 <= x && x <= cx+w/2
&& cy-h/2 <= y && y <= cy+h/2
inShape (x,y) (Octagon (cx,cy) w)
= cx-w/2 <= x && x <= cx+w/2
&& cy-w/2 <= y && y <= cy+w/2
&& abs (x-cx) + abs (y-cy) <= w / sqrt 2
inShape p (Union s t) = inShape p s || inShape p t
description :: Shape -> String
description (Circle c r) = "a circle centered at " ++ show c ++ " with radius " ++ show r
description (Rectangle c w h) = "a rectangle centered at " ++ show c ++ " with width " ++ show w ++ " and height " ++ show h
description (Octagon c w) = "an octagon centered at " ++ show c ++ " with width " ++ show w
description (Union s t) = "the union of " ++ description s ++ " and " ++ description t
driver :: String -> IO ()
driver spec = do
putStrLn "---BEGIN---"
let res = parse (shape <* eof) "(string)" spec
case res of
Left err -> error (errorBundlePretty err)
Right s -> do
putStrLn $ unlines $ render (bbox s) (40,20) s
putStrLn $ description s
putStrLn "---END---"
main :: IO ()
main = do
driver "UR(0,0)2,2UC(1,1)0.5C(-1,1)0.5"
Solution #2
A second method of writing this program is to define a Shape class and a collection of instances of this class representing each possible shape. We might spec out the shapes we want to support as follows, without defining any functionality just yet:
class Shape s
data Circle = Circle Pos Scalar deriving (Show)
instance Shape Circle
data Rectangle = Rectangle Pos Scalar Scalar deriving (Show)
instance Shape Rectangle
Now, to parse the parse the shape specification, we'll use Megaparsec. We can't write a Parser Shape, because Shape is a type class. We can write a method for parsing the individual shapes however. This involves adding lines to the class and instances:
class Shape s where
parseShape :: Parser s
instance Shape Circle where
parseShape = Circle <$ char 'C' <*> pos <*> scalar
instance Shape Rectangle where
parseShape = Rectangle <$ char 'R' <*> pos <*> scalar <* char ',' <*> scalar
We can conveniently parse an arbitrary shape by definining an existential type:
data SomeShape = forall s. Shape s => SomeShape s
As tempting as it might seem, the following does not work at all:
someShape :: Parser SomeShape
someShape = SomeShape <$> parseShape
There is, unfortunately, no way to avoid enumerating the individual shapes like so:
someShape :: Parser SomeShape
someShape = SomeShape <$> (parseShape :: Parser Circle)
<|> SomeShape <$> (parseShape :: Parser Rectangle)
Now that we can parse shapes, we'll want to calculate a bounding box. This involves adding the Box type and three lines of the bbox definition to the class and two instances:
data Box = Box Pos Pos deriving (Show)
class Shape s where
...
bbox :: s -> Box
instance Shape Circle where
...
bbox (Circle (x,y) r) = Box (x-r,y-r) (x+r,y+r)
instance Shape Rectangle where
...
bbox (Rectangle (x,y) w h) = Box (x-w/2,y-h/2) (x+w/2,y+h/2)
The rendering function is much the same as before, except for its type signature:
render :: (Shape s) => Box -> (Int, Int) -> s -> Raster
The render function requires an inShape method, which involves adding the three lines of the inShape definition to the class and two instances.
The driver needs to be modified to handle the existential shape. In this case, the fix is easy -- just an additional pattern match on SomeShape:
driver :: String -> IO ()
driver spec = do
putStrLn "---BEGIN---"
let res = parse (someShape <* eof) "(string)" spec
case res of
Left err -> error (errorBundlePretty err)
Right (SomeShape s) -> putStrLn $ unlines $ render (bbox s) (40,20) s
putStrLn "---END---"
The full program follows:
{-# OPTIONS_GHC -Wall #-}
import Data.Void
import Text.Megaparsec hiding (Pos)
import Text.Megaparsec.Char
data Box = Box Pos Pos deriving (Show)
class Shape s where
parseShape :: Parser s
bbox :: s -> Box
inShape :: Pos -> s -> Bool
type Pos = (Float, Float)
type Scalar = Float
data Circle = Circle Pos Scalar deriving (Show)
instance Shape Circle where
parseShape = Circle <$ char 'C' <*> pos <*> scalar
bbox (Circle (x,y) r) = Box (x-r,y-r) (x+r,y+r)
inShape (x,y) (Circle (cx,cy) r) = sq (x-cx) + sq (y-cy) <= sq r
where sq a = a*a
data Rectangle = Rectangle Pos Scalar Scalar deriving (Show)
instance Shape Rectangle where
parseShape = Rectangle <$ char 'R' <*> pos <*> scalar <* char ',' <*> scalar
bbox (Rectangle (x,y) w h) = Box (x-w/2,y-h/2) (x+w/2,y+h/2)
inShape (x,y) (Rectangle (cx,cy) w h)
= cx-w/2 <= x && x <= cx+w/2
&& cy-h/2 <= y && y <= cy+h/2
type Parser = Parsec Void String
pos :: Parser Pos
pos = (,) <$ char '(' <*> scalar <* char ',' <*> scalar <* char ')'
scalar :: Parser Scalar
scalar = do
sgn <- option 1 (-1 <$ char '-')
dig1 <- digits
dig2 <- option "" ((:) <$> char '.' <*> digits)
return $ sgn * read (dig1 ++ dig2)
where digits = some digitChar
data SomeShape = forall s. Shape s => SomeShape s
someShape :: Parser SomeShape
someShape = SomeShape <$> (parseShape :: Parser Circle)
<|> SomeShape <$> (parseShape :: Parser Rectangle)
type Raster = [[Char]]
render :: (Shape s) => Box -> (Int, Int) -> s -> Raster
render (Box (x1,y1) (x2,y2)) (cx,cy) s
= [[ if inShape (x,y) s then 'X' else ' '
| x <- [x1',x1'+delta..x2']] | y <- [y1',y1'-delta*caspect..y2']]
where caspect = 2 -- font character aspect ratio
expand = 1.2 -- expansion of raster beyond bounding box
delta = max ((x2-x1) / fromIntegral cx) ((y2-y1) / fromIntegral cy / caspect)
x1' = (x1+x2)/2 - expand*cx' * delta
x2' = (x1+x2)/2 + expand*cx' * delta
y1' = (y1+y2)/2 + expand*cy' * delta * caspect
y2' = (y1+y2)/2 - expand*cy' * delta * caspect
cx' = fromIntegral cx / 2
cy' = fromIntegral cy / 2
driver :: String -> IO ()
driver spec = do
putStrLn "---BEGIN---"
let res = parse (someShape <* eof) "(string)" spec
case res of
Left err -> error (errorBundlePretty err)
Right (SomeShape s) -> putStrLn $ unlines $ render (bbox s) (40,20) s
putStrLn "---END---"
main :: IO ()
main = do
driver "C(3,8)8" -- circle radius 8 centered at (3,8)
driver "R(-1,6)8,3" -- rectangle centered at (-1,6) w/ dim 8 by 3
There's not a tremendous amount of difference between this type class based version and the first ADT version above. Using a natural, common syntax, it's about 15% longer, as measured by lines or characters, it requires the use of an existential data type, and it includes some complex distractions related to that type.
Now, suppose we want to add an Octagon. The type class implementation should provide much cleaner extensibility for new shapes. We need to add the type and its instance:
data Octagon = Octagon Pos Scalar deriving (Show)
instance Shape Octagon where
parseShape = Octagon <$ char 'O' <*> pos <*> scalar
bbox (Octagon (x,y) w) = Box (x-w/2,y-w/2) (x+w/2,y+w/2)
inShape (x,y) (Octagon (cx,cy) w)
= cx-w/2 <= x && x <= cx+w/2
&& cy-w/2 <= y && y <= cy+w/2
&& abs (x-cx) + abs (y-cy) <= w / sqrt 2
Unfortunately, we need to separately add the octagon to the enumeration in the shape parser:
someShape :: Parser SomeShape
someShape = SomeShape <$> (parseShape :: Parser Circle)
<|> SomeShape <$> (parseShape :: Parser Rectangle)
<|> SomeShape <$> (parseShape :: Parser Octagon)
This seems like defect. We ought to specify a master list of shapes in one place that can be sequenced at runtime to iterate over all parseShape methods (as well as other methods we might later add that require similar enumeration). A straightforward way of doing this is to define a function that converts a generic Shape operation into a sequence of specialized operations across concrete shapes. That is:
overShapes :: (forall s. Shape s => Proxy s -> a) -> [a]
overShapes op =
[ op (Proxy #Circle)
, op (Proxy #Rectangle)
, op (Proxy #Octagon)]
Now we can write shape succintly as:
someShape :: Parser SomeShape
someShape = asum (overShapes op)
where op :: forall s. (Shape s) => Proxy s -> Parser SomeShape
op _ = SomeShape <$> parseShape #s
This should really pay off now that we're ready to add unions. We will only need to define a Union type and instance containing all the supporting functionality in one place and then remember to add it to overShapes for a seamless extension.
Unfortunately, the obvious definition:
data Union = Union Shape Shape deriving (Show)
doesn't work, since Shape is a type class. We might start with something like:
data Union s t = Union s t deriving (Show)
instance (Shape s, Shape t) => Shape (Union s t) where
parseShape = ...
bbox (Union s t)
= let Box (x1,y1) (x2,y2) = bbox s
Box (x3,y3) (x4,y4) = bbox t
in Box (min x1 x3, min y1 y3) (max x2 x4, max y2 y4)
inShape p (Union s t) = inShape p s || inShape p t
Now, when we try to define parseShape, we run into a bit of a problem. We can certainly define:
instance (Shape s, Shape t) => Shape (Union s t) where
parseShape = Union <$ char 'U' <*> parseShape <*> parseShape
but it quickly becomes apparent that we can't make any use of it. The parseShape parser can only parse a known shape, like Union Circle Rectangle. If we want to parse an arbitrary union, we need to parse its components not using parseShape but instead by using someShape and then constructing an existential SomeShape for the Union, which can't expose its subshapes in the Union type. So, we'll probably need to write:
data Union = Union SomeShape SomeShape
instance Shape Union where
parseShape = Union <$ char 'U' <*> someShape <*> someShape
bbox (Union (SomeShape s) (SomeShape t))
= let Box (x1,y1) (x2,y2) = bbox s
Box (x3,y3) (x4,y4) = bbox t
in Box (min x1 x3, min y1 y3) (max x2 x4, max y2 y4)
inShape p (Union (SomeShape s) (SomeShape t)) = inShape p s || inShape p t
As mentioned, we'll need to add it to the overShapes function:
overShapes op =
[ op (Proxy #Circle)
, op (Proxy #Rectangle)
, op (Proxy #Octagon)
, op (Proxy #Union)]
Still, it's nice that we were able to add the support for Union in one place plus the overShapes enumeration.
Unfortunately, when it comes time to add our description method, the extensbility is the wrong way around. Instead of defining description in one place, as we did with the ADT implementation, we need to add a type signature and method calls to all the classes (basically 5 lines in 5 places), just as we did when adding bbox and inShape during development.
The final program is 113 lines and 3961 characters, about 20-25% longer than the ADT version. It also contains some real stinkers like:
overShapes :: (forall s. Shape s => Proxy s -> a) -> [a]
and
op :: forall s. (Shape s) => Proxy s -> Parser SomeShape
op _ = SomeShape <$> parseShape #s
but at least we can conveniently extend it by adding 7 lines in 2 places instead of 5 lines in 5 places, as long as we're only adding a new shape and not adding functionality that requires a new method -- for that, we need to add 5 lines in 5 places instead of 5 lines in 1 place.
The final program:
{-# OPTIONS_GHC -Wall #-}
import Data.Proxy
import Control.Applicative (asum)
import Data.Void
import Text.Megaparsec hiding (Pos)
import Text.Megaparsec.Char
data Box = Box Pos Pos deriving (Show)
class Shape s where
parseShape :: Parser s
bbox :: s -> Box
inShape :: Pos -> s -> Bool
description :: s -> String
overShapes :: (forall s. Shape s => Proxy s -> a) -> [a]
overShapes op =
[ op (Proxy #Circle)
, op (Proxy #Rectangle)
, op (Proxy #Octagon)
, op (Proxy #Union)]
type Pos = (Float, Float)
type Scalar = Float
data Circle = Circle Pos Scalar deriving (Show)
instance Shape Circle where
parseShape = Circle <$ char 'C' <*> pos <*> scalar
bbox (Circle (x,y) r) = Box (x-r,y-r) (x+r,y+r)
inShape (x,y) (Circle (cx,cy) r) = sq (x-cx) + sq (y-cy) <= sq r
where sq a = a*a
description (Circle c r) = "a circle centered at " ++ show c ++ " with radius " ++ show r
data Rectangle = Rectangle Pos Scalar Scalar deriving (Show)
instance Shape Rectangle where
parseShape = Rectangle <$ char 'R' <*> pos <*> scalar <* char ',' <*> scalar
bbox (Rectangle (x,y) w h) = Box (x-w/2,y-h/2) (x+w/2,y+h/2)
inShape (x,y) (Rectangle (cx,cy) w h)
= cx-w/2 <= x && x <= cx+w/2
&& cy-h/2 <= y && y <= cy+h/2
description (Rectangle c w h) = "a rectangle centered at " ++ show c ++ " with width " ++ show w ++ " and height " ++ show h
data Octagon = Octagon Pos Scalar deriving (Show)
instance Shape Octagon where
parseShape = Octagon <$ char 'O' <*> pos <*> scalar
bbox (Octagon (x,y) w) = Box (x-w/2,y-w/2) (x+w/2,y+w/2)
inShape (x,y) (Octagon (cx,cy) w)
= cx-w/2 <= x && x <= cx+w/2
&& cy-w/2 <= y && y <= cy+w/2
&& abs (x-cx) + abs (y-cy) <= w / sqrt 2
description (Octagon c w) = "an octagon centered at " ++ show c ++ " with width " ++ show w
data Union = Union SomeShape SomeShape
instance Shape Union where
parseShape = Union <$ char 'U' <*> someShape <*> someShape
bbox (Union (SomeShape s) (SomeShape t))
= let Box (x1,y1) (x2,y2) = bbox s
Box (x3,y3) (x4,y4) = bbox t
in Box (min x1 x3, min y1 y3) (max x2 x4, max y2 y4)
inShape p (Union (SomeShape s) (SomeShape t)) = inShape p s || inShape p t
description (Union (SomeShape s) (SomeShape t)) = "the union of " ++ description s ++ " and " ++ description t
type Parser = Parsec Void String
pos :: Parser Pos
pos = (,) <$ char '(' <*> scalar <* char ',' <*> scalar <* char ')'
scalar :: Parser Scalar
scalar = do
sgn <- option 1 (-1 <$ char '-')
dig1 <- digits
dig2 <- option "" ((:) <$> char '.' <*> digits)
return $ sgn * read (dig1 ++ dig2)
where digits = some digitChar
data SomeShape = forall s. Shape s => SomeShape s
someShape :: Parser SomeShape
someShape = asum (overShapes op)
where op :: forall s. (Shape s) => Proxy s -> Parser SomeShape
op _ = SomeShape <$> parseShape #s
type Raster = [[Char]]
render :: (Shape s) => Box -> (Int, Int) -> s -> Raster
render (Box (x1,y1) (x2,y2)) (cx,cy) s
= [[ if inShape (x,y) s then 'X' else ' '
| x <- [x1',x1'+delta..x2']] | y <- [y1',y1'-delta*caspect..y2']]
where caspect = 2 -- font character aspect ratio
expand = 1.2 -- expansion of raster beyond bounding box
delta = max ((x2-x1) / fromIntegral cx) ((y2-y1) / fromIntegral cy / caspect)
x1' = (x1+x2)/2 - expand*cx' * delta
x2' = (x1+x2)/2 + expand*cx' * delta
y1' = (y1+y2)/2 + expand*cy' * delta * caspect
y2' = (y1+y2)/2 - expand*cy' * delta * caspect
cx' = fromIntegral cx / 2
cy' = fromIntegral cy / 2
driver :: String -> IO ()
driver spec = do
putStrLn "---BEGIN---"
let res = parse (someShape <* eof) "(string)" spec
case res of
Left err -> error (errorBundlePretty err)
Right (SomeShape s) -> do
putStrLn $ unlines $ render (bbox s) (40,20) s
putStrLn $ description s
putStrLn "---END---"
main :: IO ()
main = do
driver "UR(0,0)2,2UC(1,1)0.5C(-1,1)0.5"
Conclusions
The bottom line is that the ADT solution is straightforward to develop, understand, and extend. The type class solution pays a notable price in unnecessary complexity during development, in terms of both unusual type system features and the tendency to break the natural flow of development by splitting functions (which are the natural unit of development for functional programs) across separate instances, all for the promise of improved "extensibility" of the final program that is rarely realized in the real world.
I didn't time myself, but I'd estimate that my development time was about the same for each version. Considering that I wrote the ADT version first and copied and pasted everything I could from that to the type class version, this is quite an indictment of the type class version. The time I spent thinking through the core logic of the program (the overall design, the technical aspects of rendering the shapes, parsing a scalar in the most awkward way possible, etc.) plus the ADT implementation itself took about as much effort as thinking through the stupidities of the SomeShape type and fighting the Haskell type system to get the Union to work.
My larger experience with Haskell programming is that this is how it usually goes with ADT-based versus type-class-based designs, except it gets worse for bigger programs.

Related

String to double?

So for my assignment I have to create a couple functions (new to Haskell).
I can do the following quite easily
data Shape
= Circle Double -- radius
| Square Double -- length
| Rectangle Double Double --length and width
deriving (Show)
showShape :: Shape -> String
showShape (Circle r) = "circle of radius " ++ show r
showShape (Square l) = "square of length " ++ show l
showShape (Rectangle l w) = "rectangle of length " ++ show l ++ " and width " ++ show w
area :: Shape -> String
area (Circle r) =
"Circle of radius " ++ show r ++ " has an area of " ++ show (pi * r * r)
Except my assignment specifically wants me to use
area :: Shape -> Double
How do I go about this?
If you do :
data Shape
= Circle Double -- radius
| Square Double -- length
| Rectangle Double Double --length and width
deriving (Show)
showShape :: Shape -> String
showShape (Circle r) = "circle of radius " ++ show r
showShape (Square l) = "square of length " ++ show l
showShape (Rectangle l w) = "rectangle of length " ++ show l ++ " and width " ++ show w
area :: Shape -> Double
area (Circle r) = (pi * r * r)
it works for me.
I think your problem is that you try to do two things in the same function :
Calculate the area
Return a nice string info with the area
Your function, with the area :: Shape -> Double should only calculate the area.

Does Haskell provides a way to evaluate IO monad immediately?

I am currently making a ray tracing program with Haskell. As I am a very beginner of Haskell, I don't understand the evaluation strategy of IO monad clearly.
The problem is the memory usage of a long list of "IO a", which is "IO Vec" in my code.
Each element of the list is computed by a recursive function that compute IO Vec which represents the color for a pixel. Therefore, the length of the list is equals to width x height.
In addition, I take multiple samples for a pixels. As a whole, the function radiance to compute pixel value is called width x height x samples times.
First I was implemented this program simply by using list comprehension. The code is like,
main = do
...
let ray = (compute ray for every pair of [0..w-1], [0..h-1]
pixels <- sequence [ (sumOfRadiance scene ray samples) | ray <- rays]
In my understanding, as pixels is not used before it is written to a file, Haskell stores some data for function call inside pixels which is an array of IO Vec. Finally, memory consumption increases by calling recursive function radiance to compute pixel values.
If I change the program to evaluate the pixel value one by one using unsafePerformIO can prevent this strange use of memory space.
main = do
...
let ray = (compute ray for every pair of [0..w-1], [0..h-1]
let pixels = [ (unsafePerformIO (sumOfRadiance scene ray samples)) | ray <- rays]
I know unsafePerformIO is a bad solution, so I'd like to know if Haskell provides another way to evaluate inside of IO monad immediately. The following is the whole of my code (Sorry, it's a bit long...)
Thank you for your help.
-- Small path tracing with Haskell
import System.Environment
import System.Random.Mersenne
import System.IO.Unsafe
import Control.Monad
import Codec.Picture
import Data.Time
import qualified Data.Word as W
import qualified Data.Vector.Storable as V
-- Parameters
eps :: Double
eps = 1.0e-4
inf :: Double
inf = 1.0e20
nc :: Double
nc = 1.0
nt :: Double
nt = 1.5
-- Vec
data Vec = Vec (Double, Double, Double) deriving (Show)
instance (Num Vec) where
(Vec (x, y, z)) + (Vec (a, b, c)) = Vec (x + a, y + b, z + c)
(Vec (x, y, z)) - (Vec (a, b, c)) = Vec (x - a, y - b, z - c)
(Vec (x, y, z)) * (Vec (a, b, c)) = Vec (x * a, y * b, z * c)
abs = undefined
signum = undefined
fromInteger x = Vec (dx, dx, dx) where dx = fromIntegral x
x :: Vec -> Double
x (Vec (x, _, _)) = x
y :: Vec -> Double
y (Vec (_, y, _)) = y
z :: Vec -> Double
z (Vec (_, _, z)) = z
mul :: Vec -> Double -> Vec
mul (Vec (x, y, z)) s = Vec (x * s, y * s, z * s)
dot :: Vec -> Vec -> Double
dot (Vec (x, y, z)) (Vec (a, b, c)) = x * a + y * b + z * c
norm :: Vec -> Vec
norm (Vec (x, y, z)) = Vec (x * invnrm, y * invnrm, z * invnrm)
where invnrm = 1 / sqrt (x * x + y * y + z * z)
cross :: Vec -> Vec -> Vec
cross (Vec (x, y, z)) (Vec (a, b, c)) = Vec (y * c - b * z, z * a - c * x, x * b - a * y)
-- Ray
data Ray = Ray (Vec, Vec) deriving (Show)
org :: Ray -> Vec
org (Ray (org, _)) = org
dir :: Ray -> Vec
dir (Ray (_, dir)) = dir
-- Material
data Refl = Diff
| Spec
| Refr
deriving Show
-- Sphere
data Sphere = Sphere (Double, Vec, Vec, Vec, Refl) deriving (Show)
rad :: Sphere -> Double
rad (Sphere (rad, _, _, _, _ )) = rad
pos :: Sphere -> Vec
pos (Sphere (_ , p, _, _, _ )) = p
emit :: Sphere -> Vec
emit (Sphere (_ , _, e, _, _ )) = e
col :: Sphere -> Vec
col (Sphere (_ , _, _, c, _ )) = c
refl :: Sphere -> Refl
refl (Sphere (_ , _, _, _, refl)) = refl
intersect :: Sphere -> Ray -> Double
intersect sp ray =
let op = (pos sp) - (org ray)
b = op `dot` (dir ray)
det = b * b - (op `dot` op) + ((rad sp) ** 2)
in
if det < 0.0
then inf
else
let sqdet = sqrt det
t1 = b - sqdet
t2 = b + sqdet
in ansCheck t1 t2
where ansCheck t1 t2
| t1 > eps = t1
| t2 > eps = t2
| otherwise = inf
-- Scene
type Scene = [Sphere]
sph :: Scene
sph = [ Sphere (1e5, Vec ( 1e5+1, 40.8, 81.6), Vec (0.0, 0.0, 0.0), Vec (0.75, 0.25, 0.25), Diff) -- Left
, Sphere (1e5, Vec (-1e5+99, 40.8, 81.6), Vec (0.0, 0.0, 0.0), Vec (0.25, 0.25, 0.75), Diff) -- Right
, Sphere (1e5, Vec (50.0, 40.8, 1e5), Vec (0.0, 0.0, 0.0), Vec (0.75, 0.75, 0.75), Diff) -- Back
, Sphere (1e5, Vec (50.0, 40.8, -1e5+170), Vec (0.0, 0.0, 0.0), Vec (0.0, 0.0, 0.0), Diff) -- Front
, Sphere (1e5, Vec (50, 1e5, 81.6), Vec (0.0, 0.0, 0.0), Vec (0.75, 0.75, 0.75), Diff) -- Bottom
, Sphere (1e5, Vec (50,-1e5+81.6,81.6), Vec (0.0, 0.0, 0.0), Vec (0.75, 0.75, 0.75), Diff) -- Top
, Sphere (16.5, Vec (27, 16.5, 47), Vec (0.0, 0.0, 0.0), Vec (1,1,1) `mul` 0.999, Spec) -- Mirror
, Sphere (16.5, Vec (73, 16.5, 78), Vec (0.0, 0.0, 0.0), Vec (1,1,1) `mul` 0.999, Refr) -- Glass
, Sphere (600, Vec (50, 681.6 - 0.27, 81.6), Vec (12, 12, 12), Vec (0, 0, 0), Diff) ] -- Light
-- Utility functions
clamp :: Double -> Double
clamp = (max 0.0) . (min 1.0)
isectWithScene :: Scene -> Ray -> (Double, Int)
isectWithScene scene ray = foldr1 (min) $ zip [ intersect sph ray | sph <- scene ] [0..]
nextDouble :: IO Double
nextDouble = randomIO
lambert :: Vec -> Double -> Double -> (Vec, Double)
lambert n r1 r2 =
let th = 2.0 * pi * r1
r2s = sqrt r2
w = n
u = norm $ (if (abs (x w)) > eps then Vec (0, 1, 0) else Vec (1, 0, 0)) `cross` w
v = w `cross` u
uu = u `mul` ((cos th) * r2s)
vv = v `mul` ((sin th) * r2s)
ww = w `mul` (sqrt (1.0 - r2))
rdir = norm (uu + vv + ww)
in (rdir, 1)
reflect :: Vec -> Vec -> (Vec, Double)
reflect v n =
let rdir = v - (n `mul` (2.0 * n `dot` v))
in (rdir, 1)
refract :: Vec -> Vec -> Vec -> Double -> (Vec, Double)
refract v n orn rr =
let (rdir, _) = reflect v orn
into = (n `dot` orn) > 0
nnt = if into then (nc / nt) else (nt / nc)
ddn = v `dot` orn
cos2t = 1.0 - nnt * nnt * (1.0 - ddn * ddn)
in
if cos2t < 0.0
then (rdir, 1.0)
else
let tdir = norm $ ((v `mul` nnt) -) $ n `mul` ((if into then 1 else -1) * (ddn * nnt + (sqrt cos2t)))
a = nt - nc
b = nt + nc
r0 = (a * a) / (b * b)
c = 1.0 - (if into then -ddn else (tdir `dot` n))
re = r0 + (1 - r0) * (c ** 5)
tr = 1.0 - re
pp = 0.25 + 0.5 * re
in
if rr < pp
then (rdir, (pp / re))
else (tdir, ((1.0 - pp) / tr))
radiance :: Scene -> Ray -> Int -> IO Vec
radiance scene ray depth = do
let (t, i) = (isectWithScene scene ray)
if inf <= t
then return (Vec (0, 0, 0))
else do
r0 <- nextDouble
r1 <- nextDouble
r2 <- nextDouble
let obj = (scene !! i)
let c = col obj
let prob = (max (x c) (max (y c) (z c)))
if depth >= 5 && r0 >= prob
then return (emit obj)
else do
let rlt = if depth < 5 then 1 else prob
let f = (col obj)
let d = (dir ray)
let x = (org ray) + (d `mul` t)
let n = norm $ x - (pos obj)
let orn = if (d `dot` n) < 0.0 then n else (-n)
let (ndir, pdf) = case (refl obj) of
Diff -> (lambert orn r1 r2)
Spec -> (reflect d orn)
Refr -> (refract d n orn r1)
nextRad <- (radiance scene (Ray (x, ndir)) (succ depth))
return $ ((emit obj) + ((f * nextRad) `mul` (1.0 / (rlt * pdf))))
toByte :: Double -> W.Word8
toByte x = truncate (((clamp x) ** (1.0 / 2.2)) * 255.0) :: W.Word8
accumulateRadiance :: Scene -> Ray -> Int -> Int -> IO Vec
accumulateRadiance scene ray d m = do
let rays = take m $ repeat ray
pixels <- sequence [radiance scene r 0 | r <- rays]
return $ (foldr1 (+) pixels) `mul` (1 / fromIntegral m)
main :: IO ()
main = do
args <- getArgs
let argc = length args
let w = if argc >= 1 then (read (args !! 0)) else 400 :: Int
let h = if argc >= 2 then (read (args !! 1)) else 300 :: Int
let spp = if argc >= 3 then (read (args !! 2)) else 4 :: Int
startTime <- getCurrentTime
putStrLn "-- Smallpt.hs --"
putStrLn $ " width = " ++ (show w)
putStrLn $ " height = " ++ (show h)
putStrLn $ " spp = " ++ (show spp)
let dw = fromIntegral w :: Double
let dh = fromIntegral h :: Double
let cam = Ray (Vec (50, 52, 295.6), (norm $ Vec (0, -0.042612, -1)));
let cx = Vec (dw * 0.5135 / dh, 0.0, 0.0)
let cy = (norm $ cx `cross` (dir cam)) `mul` 0.5135
let dirs = [ norm $ (dir cam) + (cy `mul` (y / dh - 0.5)) + (cx `mul` (x / dw - 0.5)) | y <- [dh-1,dh-2..0], x <- [0..dw-1] ]
let rays = [ Ray ((org cam) + (d `mul` 140.0), (norm d)) | d <- dirs ]
let pixels = [ (unsafePerformIO (accumulateRadiance sph r 0 spp)) | r <- rays ]
let pixelData = map toByte $! pixels `seq` (foldr (\col lst -> [(x col), (y col), (z col)] ++ lst) [] pixels)
let pixelBytes = V.fromList pixelData :: V.Vector W.Word8
let img = Image { imageHeight = h, imageWidth = w, imageData = pixelBytes } :: Image PixelRGB8
writePng "image.png" img
endTime <- getCurrentTime
print $ diffUTCTime endTime startTime
First, I think there is an error. When you talk about going from
pixels <- sequence [ (sumOfRadiance scene ray samples) | ray <- rays]
to
pixels <- sequence [ (unsafePerformIO (sumOfRadiance scene ray samples)) | ray <- rays]
that doesn't make sense. The types shouldn't match up -- sequence only makes sense if you are combining a bunch of things of type m a. It would be correct to do
let pixels = [ unsafePerformIO (sumOfRadiance scene ray samples) | ray <- rays ]
I will somewhat cavalierly assume that that is what you did and you simply made a mistake when entering your question.
If this is the case, then what you are actually looking for is a way to execute IO actions more lazily, not more immediately. The sequence call forces all the actions to be run right then, whereas the unsafePerformIO version simply creates a list of un-run actions (and indeed the list itself is generated lazily so it doesn't exist all at once), and the actions are run individually as their results are needed.
It appears that the reason you need IO is to generate random numbers. Randomness can be kind of a pain -- usually MonadRandom does the job, but it still creates a sequential dependence between actions and may still not be lazy enough (I'd give it a try -- if you use it you get reproducibility -- the same seed gives the same results, even after refactorings that respect the monad laws).
If MonadRandom doesn't work and you need to generate random numbers in a more on-demand way, the way would be to make your own randomness monad which does the same thing as your unsafePerformIO solution, but in a way that is properly encapsulated. I'm going to show you the way I consider to be the Haskell Way To Cheat. First, a lovely pure implementation sketch:
-- A seed tells you how to generate random numbers
data Seed = ...
splitSeed :: Seed -> (Seed, Seed)
random :: Seed -> Double
-- A Cloud is a probability distribution of a's, or an a which
-- depends on a random seed. This monad is just as lazy as a
-- pure computation.
newtype Cloud a = Cloud { runCloud :: Seed -> a }
deriving (Functor)
instance Monad Cloud where
return = Cloud . const
m >>= f = Cloud $ \seed ->
let (seed1, seed2) = splitSeed seed in
runCloud (f (runCloud m seed1)) seed2
(I think I got that right. The point is that at every bind you split the seed in two and pass one to the left and the other to the right.)
Now this is a perfectly pure implementation of randomness... with a couple catches. (1) there is no non-trivial splitSeed which will strictly respect the monad laws, and (2) even if we allow the laws to be broken, random number generators based on splitting can be pretty slow. But if we give up determinism, if all we care about is that we get a good sampling from the distribution rather than the exact same result, then we don't need to strictly respect the monad laws. And at that point we cheat and pretend there is a suitable Seed type:
data Seed = Seed
splitSeed Seed = (Seed, Seed)
-- Always NOINLINE functions with unsafePerformIO to keep the
-- optimizer from messing with you.
{-# NOINLINE random #-}
random Seed = unsafePerformIO randomIO
We should hide this inside a module to keep the abstraction barrier clear. Cloud and runCloud should not be exposed since they allow us to violate purity; expose only
runCloudIO :: Cloud a -> IO a
runCloudIO = return . runCloud
which doesn't technically need IO, but communicates that this will not be deterministic. Then you can build up whatever you need as a value in the Cloud monad, and run it once in your main program.
You might ask why we have a Seed type at all if it doesn't have any information. Well, I think splitSeed is just a nod to purity and isn't actually doing anything -- you could remove it -- but we need Cloud to be a function type so that the implicit caching of laziness doesn't break our semantics. Otherwise
let foo = random in liftM2 (,) foo foo
would always return a pair with two identical components, since the random value was really associated with foo. I am not sure about these things since at this point we are at war with the optimizer, it takes some experimentation.
Happy cheating. :-)

Haskell class instance

It's my first exercise to understand the classes in Haskell. My problem is how to define the functions that I have declared in the class and how to test them by terminal ghci.
I explain step by step what I did:
type Point2d = (Int, Int) -- point
type Vector2d = (Int, Int) -- vector
data Shape =
Line Point2d Point2d
| Triangle Point2d Point2d Point2d
deriving (Eq, Show)
class ClassShape s where
name :: s -> String
perim :: s -> Int -- given a CShape calculates the perimeter
move :: s -> Vector2d -> s
Now, I declare s as ClassShape instance, by implementing the corresponding functions.
nameShape :: Shape s -> String
nameShape Line = "Line"
nameShape Triangle = "Triangle"
perimShape :: Shape s -> Int
perimShape Line a b = 999 -- ...
perimShape Triangle a b c = 999 -- ...
Here's my problem: how should I declare the functions? I just need to see an "example" to understand the concept.
The error that Haskell returns is:
`Shape' is applied to too many type arguments
In the type signature for `nameShape':
nameShape :: Shape s -> String
`Shape' is applied to too many type arguments
In the type signature for `perimShape':
perimShape :: Shape s -> Int
Then, how do I test the program on Haskell?
Thanks to all.
Note that nameShape function will not work, because there is no Shape s type defined. Remember that s is a type variable. Only if you have defined Shape s type constructor you can use them. You have defined Shape type in your definition but not Shape s. For defining instance of typeclass, you have to do something like this:
instance ClassShape Shape where
name (Line _ _) = "Line"
name (Triangle _ _ _) = "Triangle"
perim (Line x y) = undefined -- Calculate perimiter using x and y
perim (Triangle x y z) = undefined
move (Line x y) = undefined
move (Triangle x y z) = undefined
You have to fill the undefined with working parts.
You're making a common confusion of early Haskell programmers: using two different things which work in related ways (sum types and classes) to do the same thing in two different ways. Thus there are two problems: the "little" problem (what does this error mean?) and the "big" problem (why is your code shaped like this?).
The Little Problem
You wrote Shape s when you meant to just write Shape. The way you have defined Shape, it has kind * (that is, it is a concrete type) rather than kind * -> *, which is the kind of adjectives -- things like "a list of" or "a pair of" which are abstract until you give them a concrete type to modify ("a list of strings" is concrete, "a list of" is abstract). When you write Shape s you are applying Shape as an adjective to a type variable s but it's not an adjective; it's a noun.
That is why you get the error:
`Shape' is applied to too many type arguments
Side note: you may be used to languages where the error message usually is not very well-related to the actual problem. In Haskell usually the compiler tells you exactly what is wrong, as it did in this case.
The Big Problem
Type classes are collections of unrelated types which can do the same things. The type class syntax passes an implicit context as a "constraint", this context can be implicit because it belongs to the type.
You may need to read that last paragraph a few times in a quiet corner. Basically I mean to say that you can do the same thing as a type class with a data constructor for the context:
data EqOrd s = EqOrdLib {getEq :: s -> s -> Bool, getCmp :: s -> s -> Ordering}
-- this is just provided to us as a primitive by Haskell
intEOL :: EqOrd Int
intEOL = EqOrdLib (==) compare
-- but we can then define things like this:
listEOL :: EqOrd x -> EqOrd [x]
listEOL (EqOrdLib base_eq base_cmp) = EqOrdLib list_eq list_cmp where
list_cmp [] [] = EQ
list_cmp (_:_) [] = GT
list_cmp [] (_:_) = LT
list_cmp (x:xs) (y:ys) = case base_cmp x y of
LT -> LT
GT -> GT
EQ -> list_cmp xs ys
list_eq xs ys = list_cmp xs ys == EQ
Now to use that sort of context, you would have to write explicitly:
quicksort :: EqOrd x -> [x] -> [x]
quicksort _ [] = []
quicksort lib (p:els) = quicksort lib lesser ++ [p] ++ quicksort lib greater
where cmp = getCmp lib
p_less_than x = cmp x p == LT
p_gte x = not . p_less_than
greater = filter p_less_than els
lesser = filter p_gte els
See, we explicitly pass in this library of functions lib and explicitly pull out the comparison function cmp = getCmp lib.
Type classes allow us to implicitly pass the library of functions, by stating up-front that the type itself only has one such library. We pass the library as a "constraint", so instead of EqOrd x -> [x] -> [x] you write Ord x => [x] -> [x] with the "fat arrow" of constraints. But secretly it means "when you ask me to use the < function on two values of type x, I know implicitly what library to get that function from and will get that function for you."
Now: you have one type, Shape, so you don't need typeclasses. (Go back to the first paragraph above: Type classes are collections of unrelated types which can do the same things.
If you want to do type classes then instead of the sum-type for Shape, let's define n-dimensional vectors of different types:
class Vector v where
(*.) :: (Num r) => r -> v r -> v r
(.+.) :: (Num r) => v r -> v r -> v r
norm :: (Num r, Floating r) => v r -> r
-- another advantage of type classes is *default declarations* like:
(.-.) :: (Num r) => v r -> v r -> v r
v1 .-. v2 = v1 .+. (-1 *. v2)
data V2D r = V2D r r deriving (Eq, Show)
instance Vector V2D where
s *. V2D x y = V2D (s * x) (s * y)
V2D x1 y1 .+. V2D x2 y2 = V2D (x1 + x2) (y1 + y2)
norm (V2D x y) = sqrt (x^2 + y^2)
data V3D r = V3D r r r deriving (Eq, Show)
instance Vector V3D where
s *. V3D x y z = V3D (s * x) (s * y) (s * z)
V3D x1 y1 z1 .+. V3D x2 y2 z2 = V3D (x1 + x2) (y1 + y2) (z1 + z2)
norm (V3D x y z) = sqrt (x^2 + y^2 + z^2)
Then we can write things like:
newtype GeneralPolygon v r = Poly [v r]
perimeter :: (Num r, Floating r, Vector v) -> GeneralPolygon v r -> r
perimeter (Poly []) = 0
perimeter (Poly (x : xs)) = foldr (+) 0 (map norm (zipWith (.-.) (x : xs) (xs ++ [x])))
translate :: (Vector v, Num r) => GeneralPolygon v r -> v r -> GeneralPolygon v r
translate (Poly xs) v = Poly (map (v .+.) xs)
Making Typeclasses Work For You
Now if you really want to, you can also unwrap your sum-type data declaration into a bunch of data declarations:
data Line = Line Point2d Point2d deriving (Eq, Show)
data Square = Square Point2d Point2d deriving (Eq, Show)
data Triangle = Triangle Point2d Point2d Point2d deriving (Eq, Show)
Now you can do something simple like:
class Shape s where
perim :: s -> Int
move :: s -> Vector2d -> s
Although I should say, you'll run into a problem when you want to do square roots for perimeters (sqrt is in the Floating typeclass, which Int does not have functions for, you'll want to change Int to Double or something).

Haskell applicative functor - compilation failure

I'm trying to chain together functions via the applicative functor pattern, but I'm having a problem compiling my code:
import Control.Applicative
buildMyList :: Float -> Float -> [Float]
buildMyList ul tick = [p | p <- [0,tick..ul]]
myFunctionChain :: [Float]
myFunctionChain = reverse <$> buildMyList 100 1
When I attempt to compile this I get the following compilation error:
Couldn't match type 'Float' with '[a0]'
Expected type: [[a0]]
Actual type: [Float]
In the return type of call of 'buildMyList'
It seems to me that I haven't managed to match the expected return context with the actual context. Not having enough experience in this area, I cant get any further!
The Applicative could be better explained using bulldMyList.
Assume you want to build an array of square matrix:
[(0,0), (0,1), (0,2), (1, 0) ...]. Using list comprehensions:
buildMyMatrix :: Int -> Int -> [(Int, Int)]
buildMyMatrix maxX maxY = [(x, y) | x <- [0..maxX], y <- [0..maxY]]
Using applicative combinators it can be rewritten as:
buildMyMatrix maxX maxY = pure (\x y -> (x, y)) <*> [0..maxX] <*> [0..maxY]
And according to applicative laws we can rewrite pure f <*> x = f <$> x, for all f and x:
buildMyMatrix maxX maxY = (\x y -> (x, y)) <$> [0..maxX] <*> [0..maxY]
I had to use slightly more complicated buildMyMatrix, as your buildMyList is too trivial to benefit from Applicative:
buildMyList :: Float -> Float -> [Float]
buildMyList ul tick = [p | p <- [0,tick..ul]]
buildMyList ul tick = id <$> [0,tick..ul] -- [by functor identity law]:
buildMyList ul tick = [0,tick..ul]

what is the implementation of foldGAll in wiki example

https://wiki.haskell.org/The_Monad.Reader/Issue5/Practical_Graph_Handling#foldG
i follow 1.5.2
after load trees.hs has error, just hope to run some examples in above link and export some graph jpeg or png file
Prelude> :l trees.hs
[1 of 1] Compiling Main ( trees.hs, interpreted )
trees.hs:39:22: parse error on input `='
Failed, modules loaded: none.
import Data.List
import Data.Array
--import Data.Graph
import Control.Monad
import Math.Combinat
import Math.Core.Utils
import Math.Core.Field
import Math.Algebras.VectorSpace
import Math.Algebras.Structures
--import Math.CommutativeAlgebra.GroebnerBasis
--import Math.CommutativeAlgebra.Polynomial
--import Math.Algebras.Matrix
import System.IO
import qualified Data.Map as M
type Vertex = Int
type Table a = Array Vertex a
type Graph e = Table [(e, Vertex)]
type Bounds = (Vertex, Vertex)
type Edge e = (Vertex, e, Vertex)
type Labeling a = Vertex -> a
data LabGraph n e = LabGraph (Graph e) (Labeling n)
foldGAll :: (Eq r) => r -> (Vertex -> [(e, r)] -> r) -> Graph e -> Table r
foldGAll bot f gr = finalTbl
where finalTbl = fixedPoint updateTbl initialTbl
initialTbl = listArray bnds (replicate (rangeSize bnds) bot)
fixedPoint f x = fp x
where fp z = if z == z' then z else fp z'
where z' = f z
updateTbl tbl = listArray bnds $ map recompute $ indices gr
where recompute v = f v [(b, tbl!k) | (b, k) <- gr!v]
bnds = bounds gr
foldG :: (Eq r) => r -> (Vertex -> [(e, r)] -> r) -> Graph e -> Vertex -> r
foldG i f g v = foldGAll i f g ! v
-- | Build a graph from a list of edges.
buildG :: Bounds -> [Edge e] -> Graph e
buildG bounds0 edges0 = accumArray (flip () [] bounds0 [(v, (l,w)) | (v,l,w) <- edges0]
-- | The graph obtained by reversing all edges.
transposeG :: Graph e -> Graph e
transposeG g = buildG (bounds g) (reverseE g)
edges :: Graph e -> [Edge e]
edges g = [ (v, l, w) | v <- indices g, (l, w) <- g!v ]
reverseE :: Graph e -> [Edge e]
reverseE g = [ (w, l, v) | (v, l, w) <- edges g ]
showGraphViz (LabGraph gr lab) =
"digraph name {\n" ++
"rankdir=LR;\n" ++
(concatMap showNode $ indices gr) ++
(concatMap showEdge $ edges gr) ++
"}\n"
where showEdge (from, t, to) = show from ++ " -> " ++ show to ++
" [label = \"" ++ show t ++ "\"];\n"
showNode v = show v ++ " [label = " ++ (show $ lab v) ++ "];\n"
-- | Compute the distance to v for every vertex of gr.
distsTo :: Vertex -> Graph Float -> Table Float
distsTo v gr = foldGAll infinite distance gr
where infinite = 10000000 -- well, you get the idea
distance v' neighbours
| v == v' = 0
| otherwise = minimum [distV+arcWeight | (distV, arcWeight) <- neighbours]
You seem to have several problems with indentation in that source file. Your question is also not clear, so it's hard to help you, without more information on what you are trying to achieve.
In any case, the error message is quite clear: there is a problem where ghci is unable to parse an '=' sign in line 39, character 22. Keep in mind that indentation is part of the language and that many of these parsing errors are a consequence of improper indentation.
I hope this helps.

Resources