Reputation: 433
Given my type definitions:
data Tile = Revealed | Covered deriving (Eq, Show)
data MinePit = Clean | Unsafe deriving (Eq, Show)
data Flag = Flagged | Unflagged deriving (Eq, Show)
type Square = (Tile, MinePit, Flag)
type Board = [[Square]]
I created two functions:
createBoard :: Int -> Int -> Board
createBoard 0 _ = [[]]
createBoard _ 0 = [[]]
createBoard 1 1 = [[(Covered, Clean, Unflagged)]]
createBoard n m = take n (repeat (take m (repeat (Covered, Clean, Unflagged))))
An example:
λ> createBoard 2 3
[[(Covered,Clean,Unflagged),(Covered,Clean,Unflagged),(Covered,Clean,Unflagged)],[(Covered,Clean,Unflagged),(Covered,Clean,Unflagged),(Covered,Clean,Unflagged)]]
defineIndices :: Int -> Int -> [[(Int,Int)]]
defineIndices n m = [[(i,j) | j <- [1..m]] | i <- [1..n]]
It behaves like:
λ> defineIndices 2 3
[[(1,1),(1,2),(1,3)],[(2,1),(2,2),(2,3)]]
From here, I have created a function to create a MapBoard, where the values of a particular Square could be looked up given its indicies.
type MapBoard = Map (Int, Int) Square
createMapBoard :: [[(Int,Int)]] -> [[Square]] -> MapBoard
createMapBoard indices squares = M.fromList $ zip (concat indices) (concat squares)
However, it seemed reasonable to me that I should also write a method in which I can create a MapBoard directly from a pair of Int(s), implementing my prior functions. This might look like:
createMapBoard2 :: Int -> Int -> MapBoard
createMapBoard2 n m = createMapBoard indices squares where
indices = defineIndices n m
squares = createBoard n m
However, I looked up as to whether it is possible achieve polymorphism in this situataion with createMapBoard, and have createMapBoard2 be instead createMapBoard. I discovered online that this is called Ad-Hoc Polymorphism, and one can do e.g.
class Square a where
square :: a -> a
instance Square Int where
square x = x * x
instance Square Float where
square x = x * x
Attempting to write something similar myself, the best I could come up with is the following:
class MyClass a b MapBoard where
createMapBoard :: a -> b -> MapBoard
instance createMapBoard [[(Int,Int)]] -> [[Square]] -> MapBoard where
createMapBoard indices squares = M.fromList $ zip (concat indices) (concat squares)
instance createMapBoard Int -> Int -> MapBoard where
createMapBoard n m = createMapBoard indices squares where
indices = defineIndices n m
squares = createBoard n m
Attempting to compile this results in a Compilation error:
src/minesweeper.hs:35:19-26: error: …
Unexpected type ‘MapBoard’
In the class declaration for ‘MyClass’
A class declaration should have form
class MyClass a b c where ...
|
Compilation failed.
λ>
I am confused as to why I am not allowed to use a non-algebraic type such as MapBoard in the class definition.
class MyClass a b MapBoard where
Replacing MapBoard with another algebraic type c brings about another compilation error, which is lost on me.
src/minesweeper.hs:37:10-63: error: …
Illegal class instance: ‘createMapBoard [[(Int, Int)]]
-> [[Square]] -> MapBoard’
Class instances must be of the form
context => C ty_1 ... ty_n
where ‘C’ is a class
|
src/minesweeper.hs:39:10-46: error: …
Illegal class instance: ‘createMapBoard Int -> Int -> MapBoard’
Class instances must be of the form
context => C ty_1 ... ty_n
where ‘C’ is a class
|
Compilation failed.
Is it possible for me to achieve the ad-hoc polymorphism of createMapBoard? Am I able to create a class definition which has a strict constraint that the return type must be MapBoard for all instances?
Edit:
Having corrected the syntactic errors, my code is now:
class MyClass a b where
createMapBoard :: a -> b
instance createMapBoard [[(Int,Int)]] [[Square]] where
createMapBoard indices squares = M.fromList $ zip (concat indices) (concat squares)
instance createMapBoard Int Int where
createMapBoard n m = createMapBoard indices squares where
indices = defineIndices n m
squares = createBoard n m
This leads to yet another compilation error:
src/minesweeper.hs:37:10-23: error: …
Not in scope: type variable ‘createMapBoard’
|
src/minesweeper.hs:39:10-23: error: …
Not in scope: type variable ‘createMapBoard’
|
Compilation failed.
I am inclined to believe that an error in my understanding of classes is still present.
Upvotes: 1
Views: 193
Reputation: 120711
You want to write it this way:
class MyClass a b where createMapBoard :: a -> b -> MapBoard
instance MyClass [[(Int,Int)]] [[Square]] where
createMapBoard indices squares = M.fromList $ zip ...
instance MyClass Int Int where
createMapBoard n m = createMapBoard indices squares where
...
The ... -> ... -> MapBoard
is already in the createMapBoard
method's signature, this doesn't belong in the class / instance heads.
Incidentally, I'm not convinced that it really makes sense to have a class here at all. There's nothing wrong with having two separately named createMapBoard
functions. A class only is the way to go if you can actually write polymorphic functions over it, but in this case I doubt it – you'd rather have either the concrete situation where you need the one version, or the other. There's no need for a class then, just hard-write which version it is you want.
One reason for rather going with separate functions than a class method is that it makes the type checker's work easier. As soon as the arguments of createMapBoard
are polymorphic, they could potentially have any type (at least as far as the type checker is concerned). So you can only call it with arguments whose type is fully determined elsewhere. Now, in other programming languages the type of values you might want to pass is generally fixed anyway, but in Haskell it's actually extremely common to have also polymorphic values. The simplest example is number literals – they don't have type Int
or so, but Num a => a
.
I personally find “reverse polymorphism” normally nicer to work with than “forward polymorphism”: don't make the arguments to functions polymorphic, but rather the results. This way, it's enough to have the outermost type of an expression fixed by the environment, and automatically all the subexpressions are inferred by the type checker. The other way around, you have to have all the individual expressions' types fixed, and the compiler can infer the final result type... which is hardly useful because you probably want to fix that by a signature anyway.
Upvotes: 5