Type class instance with more restrictive signature - haskell

Let's say I'm writing a data type to represent a coordinate in a cartesian coordinate system. I'd like to define functions on that data type and use Haskell type checking to prevent mixing up numbers that lie on x axis with the numbers on the y axis.
Here's the data type definition, with a phantom type that tracks the coordinate axis and two functions to construct the values:
data X
data Y
newtype Coordinate axis = Coordinate Int64 deriving (Show)
newX :: Int64 -> Coordinate X
newX = Coordinate
newY :: Int64 -> Coordinate Y
newY = Coordinate
Let's define a sliding function that slides the coordinate, either by Int value or another Coordinate value. In the first case the coordinate should keep its axis and in the second case both arguments should have the same axis:
slideByInt :: Coordinate a -> Int64 -> Coordinate a
slideByInt (Coordinate x) y = Coordinate $ x + y
slideByCoord :: Coordinate a -> Coordinate a -> Coordinate a
slideByCoord (Coordinate x) (Coordinate y) = Coordinate (x + y)
This all works great and it prevents me from confusing X and Y axis in functions that manipulate Coordinates.
My question is: how would I wrap slideByInt and slideByCoord functionality behind a class, so that I can have just with the slide function. This compiles:
class Slide a where
slide :: Coordinate x -> a -> Coordinate x
instance Slide Int64 where
slide (Coordinate x) y = Coordinate (x + y)
instance Slide (Coordinate x) where
slide (Coordinate x) (Coordinate y) = Coordinate (x + y)
but it's not as type safe as the standalone functions: slide (newX 1) (newY 1) should not type check! How would one go about fixing this, in a sense, how can I make the instance for two Coordinates less permissive than it is?
I've tried with a bunch of extensions (InstanceSigs, FunctionalDependencies, type constraints...) but nothing compiles and it's hard to tell if that's the wrong way completely or I just have to tweak my code a little bit.
Thanks...

Consider what this class declaration is stating:
class Slide a where
slide :: Coordinate x -> a -> Coordinate x
for any type x, an instance of Slide promises that given a Coordinate x and an a, it will give you back a Coordinate x. Right there is your problem. You don't want any x all the time.
I think the easiest way to achieve what you want is with a second type class parameter for the coordinate type:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
class Slide x a where
slide :: Coordinate x -> a -> Coordinate x
instance Slide X Int64 where
slide = slideByInt
instance Slide Y Int64 where
slide = slideByInt
instance Slide X (Coordinate X) where
slide = slideByCoord
instance Slide Y (Coordinate Y) where
slide = slideByCoord
The last two instances can actually be replaced with this more general instance:
{-# LANGUAGE TypeFamilies #-}
instance (a ~ b) => Slide a (Coordinate b) where
slide = slideByCoord
For what it's worth, I like to avoid using typeclasses in this manner. I don't think the immediate convenience of overloaded functions is worth the boilerplate and long-term maintenance burden. But that's just my opinion.

Related

Haskell Task Instance

I saw this exercise in a book and I am trying to do it but can't get any further.
What I'm trying to do is implement, for the data type, a function
area_t :: p -> Double
that returns the area of a general triangle.
The data type Triangle defines the function "area_t".
My current code:
data Triangle = MTriangle {
tP1 :: Point,
tP2 :: Point,
tP3 :: Point}
class Polygon p where
area_t :: p -> Float
instance Polygon Point where
instance Polygon Triangle where
area_t
Error :
Couldn't match expected type ‘Float’
with actual type ‘Point -> Point -> Float -> Point’
• The equation(s) for ‘area_t’ have three arguments,
but its type ‘Point -> Float’ has only one
The area of a point is 0, so the instance for Polygon Point (if you consider points to be polygons at all), should be:
instance Polygon Point where
area_t _ = 0
Then the code you wrote for the area of a triangle seems alright, but there's two problems:
You are pattern matching on three separate points instead of a triangle
You are producing a point instead of a plain float
A working instance might look like this:
instance Polygon Triangle where
area_t (MTriangle (MPoint x1 y1) (MPoint x2 y2) (MPoint x3 y3))
= ((x2-x1)*(y3-y1) - (x3-x1)*(y2-y1))/2

Haskell Translation Task

I'm new to programming, and I'm having trouble solving a task.
I have to use the function. In that case I have to implement it on a triangle.
I've tried different things but I'm just getting errors and that's why I'd like to ask for help.
data Triangle = Triangle {
tP1 :: Point,
tP2 :: Point,
tP3 :: Point}
deriving (Show)
First, points and vectors are two separate concepts, and should probably be distinct types, not just two different aliases for a 2-tuple.
data Point = Pt Float Float
data Vector = V Float Float
Second, your type class seems to capture the idea of translating collections of points using the same vector. The return type should then be the same as the first argument type, not hard-coded to Point.
class Polygon p where
translatePol :: p -> VectorD -> p
Now you can start simple, and define a Polygon instance for Point. (Think of a point as a degenerate polygon.)
instance Polygon Point where
translatePol (Pt x y) (Mvector v1 v2) = Pt (x + v1) (y + v2)
This can be used to define the instance for Triangle more simply.
instance Polygon Triangle where
translatePol (MTriangle p1 p2 p3) v = MTriangle (t p1) (t p2) (t p3)
where t p = translatePol p v

Single Type With Several Data Constructors vs. Several Types

Consider the following type which describes the structure of some 2-dimensional shapes:
data DrawingElem
= Rect Pos Size
| Circle Pos Radius
| Ellipse Pos Radius Radius
| Line Pos Pos
| Polygon [Pos]
| Polyline [Pos]
| Group [DrawingElem]
| Drawing [DrawingElem]
which make use of these definitions:
data Vec = Vec Double Double
type Pos = Vec
type Size = Vec
type Radius = Double
The last two data constructors of DrawingElem are somehow special, because they make tree-like arrangements of the other types possible.
mydrawing = Drawing [Rect (Vec 0 0) (Vec 10 10),
Group (Vec 30 40) [Circle (Vec 0 0) 90,
Line (Vec 0 0) (Vec 50 50)]]
Such a data structure should finally being transformed into a renderable SVG-String:
toSvg :: DrawingElem -> String
toSvg (Drawing xs) = "<svg>" ++ concatMap toSvg xs ++ "</svg>"
toSvg (Group xs) = "<g>" ++ concatMap toSvg xs ++ "</g>"
toSvg (Rect (Vec x y) (Vec w h)) = "<rect x='" ++ x ... "</rect>"
For this purpose, it looks to me it was necessary to wrap the different shapes inside the DrawingElem type. They must have the same type in order to be nested and finally rendered.
In some other occasions, I'd like them being different types however: Say for a function which sets the size of a rectangle (and this only makes sense for rectangles, the others don't have the notion of a size):
setSize :: Size -> Rect -> Rect
This of course does not work with the above definitions and must be:
setSize :: Size -> DrawingElem -> DrawingElem
setSize (Rect p s) = ..
setSize x = x
So I'd have to implement a wildcard that makes the function complete. However writing setSize someSize someCircle without getting a type error looks problematic to me.
So finally I'm struggling with wrapping the drawing Elements inside a type VS. letting them being different types. Both properties are needed in different situations as described above.
Does someone have an advice for this? Is is an either-or, or is there maybe a way to model it which takes advantage of both ways?
One option is to use another indirection layer, and have a precise type for each element:
data DrawingElem
= DERect Rect
| DECircle Circle
...
data Rect = Rect Pos Size
data Circle = Circle Pos Radius
toSvg :: DrawingElem -> String
...
setSize :: Size -> Rect -> Rect
...
As a minor downside here we need to pattern match both layers, e.g.
toSvg (DERect (Rect pos size)) = ...
A more advanced alternative could be using a GADT. This might be overkill for your task, though.
{-# LANGUAGE GADTs, DataKinds #-}
data ElemType = Rect | Circle | ...
data DrawingElem (t :: ElemType) where
DERect :: Pos -> Size -> DrawingElem Rect
DECircle :: Pos -> Radius -> DrawingElem Circle
...
-- this works on all element types t
toSvg :: DrawingElem t -> String
...
-- this works only on a rectangle element
setSize :: Size -> DrawingElem Rect -> DrawingElem Rect
setSize size (DERect pos _) = DERect pos size
I am unconvinced about whether you actually need this. If in doubt, stick with the simpler alternative.
However writing setSize someSize someCircle without getting a type error looks problematic to me.
That would be problematic indeed. To avoid that, I will suggest a third option: perhaps you don't actually need a rectangle-specific setSize function at all. An alternative approach would be keeping a single DrawingElem type, setting an initial size on rectangle construction (and the initial radius on circle construction, etc.) and using functions that can be implemented for all kinds of elements to adjust the size after construction, such as:
scale :: Double -> DrawingElem -> DrawingElem
scaleX :: Double -> DrawingElem -> DrawingElem
scaleY :: Double -> DrawingElem -> DrawingElem
That is very similar to how gloss handles shapes (cf. the relevant type definition and some picture manipulation functions). Another example worth mentioning is diagrams, which uses a very sophisticated model for pictures, with a plethora of types and classes involved, and yet handles operations such as scaling in a similar manner.

typeclasses, overloading and instance declaration

Having this:
data Rectangle = Rectangle Height Width
data Circle = Circle Radius
class Shape a where
area :: a -> Float
perimeter :: a -> Float
instance Shape Rectangle where
area (Rectangle h w) = h * w
perimeter (Rectangle h w) = 2*h+w*2
instance Shape Circle where
area (Circle r) = pi * r**2
perimeter (Circle r) = 2*pi*r
volumenPrism base height = (area base) * height
surfacePrism shape h = (area shape) * 2 + perimeter shape * h
Why cant I write this? a is a type so why doesn't this work?
instance (Shape a) => Eq a where
x==y = area x == area y
Obviously doing like this:
instance Eq Circle where
x==y = area x == area y
first for Circle and then for Rectangle works..but it seems not the right way.
What is it I don't get in all this?
Ty
The fundamental problem is that the type class instance resolution machinery doesn't backtrack. So if you write instance Shape a => Eq a, then whenever the compiler wants to find an Eq instance, the compiler will try to use this instance and for most types it won't work out because they aren't instances of Shape.
If you still really want to do this, you can add
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
at the top of your source file.
You can also work round some of the problems described above by also adding OverlappingInstances to the set of LANGUAGE pragmas, but you will still have a global instance for Eq that will cause significant confusion elsewhere in your program.
It's much better to just enumerate the instances you really need, even if it seems ugly. You can keep the boilerplate to a minimum with a helper function, e.g.
x `areaEq` y = area x == area y
and then
instance Eq Circle where
(==) = areaEq
etc.

Expand Haskell datatypes

Is it possible to expand data types with new values?
E.g.:
The following compiles:
data Axes2D = X | Y
data Axes3D = Axes2D | Z
But, the following:
data Axes2D = X | Y deriving (Show, Eq)
data Axes3D = Axes2D | Z deriving (Show, Eq)
type Point2D = (Int, Int)
type Point3D = (Int, Int, Int)
move_along_axis_2D :: Point2D -> Axes2D -> Int -> Point2D
move_along_axis_2D (x, y) axis move | axis == X = (x + move, y)
| otherwise = (x, y + move)
move_along_axis_3D :: Point3D -> Axes3D -> Int -> Point3D
move_along_axis_3D (x, y, z) axis move | axis == X = (x + move, y, z)
| axis == y = (x, y + move, z)
| otherwise = (x, y, z + move)
gives the following compiling error (move_along_axis_3D commented out doesn't give errors):
Prelude> :l expandTypes_test.hs
[1 of 1] Compiling Main ( expandTypes_test.hs, interpreted )
expandTypes_test.hs:12:50:
Couldn't match expected type `Axes3D' with actual type `Axes2D'
In the second argument of `(==)', namely `X'
In the expression: axis == X
In a stmt of a pattern guard for
an equation for `move_along_axis_3D':
axis == X
Failed, modules loaded: none.
So is it possible to make X and Y of type Axes2D as well of type Axes3D?
If it is possible: what am I doing wrong? Else: why is it not possible?
Along with what Daniel Fischer said, to expand on why this is not possible: the problems with the kind of subtyping you want run deeper than just naming ambiguity; they make type inference a lot more difficult in general. I think Scala's type inference is a lot more restricted and local than Haskell's for this reason.
However, you can model this kind of thing with the type-class system:
class (Eq t) => HasAxes2D t where
axisX :: t
axisY :: t
class (HasAxes2D t) => HasAxes3D t where
axisZ :: t
data Axes2D = X | Y deriving (Eq, Show)
data Axes3D = TwoD Axes2D | Z deriving (Eq, Show)
instance HasAxes2D Axes2D where
axisX = X
axisY = Y
instance HasAxes2D Axes3D where
axisX = TwoD X
axisY = TwoD Y
instance HasAxes3D Axes3D where
axisZ = Z
You can then use guards to "pattern-match" on these values:
displayAxis :: (HasAxes2D t) => t -> String
displayAxis axis
| axis == axisX = "X"
| axis == axisY = "Y"
| otherwise = "Unknown"
This has many of the same drawbacks as subtyping would have: uses of axisX, axisY and axisZ will have a tendency to become ambiguous, requiring type annotations that defeat the point of the exercise. It's also a fair bit uglier to write type signatures with these type-class constraints, compared to using concrete types.
There's another downside: with the concrete types, when you write a function taking an Axes2D, once you handle X and Y you know that you've covered all possible values. With the type-class solution, there's nothing stopping you from passing Z to a function expecting an instance of HasAxes2D. What you really want is for the relation to go the other way around, so that you could pass X and Y to functions expecting a 3D axis, but couldn't pass Z to functions expecting a 2D axis. I don't think there's any way to model that correctly with Haskell's type-class system.
This technique is occasionally useful — for instance, binding an OOP library like a GUI toolkit to Haskell — but generally, it's more natural to use concrete types and explicitly favour what in OOP terms would be called composition over inheritance, i.e. explicitly wrapping "subtypes" in a constructor. It's not generally much of a bother to handle the constructor wrapping/unwrapping, and it's more flexible besides.
It is not possible. Note that in
data Axes2D = X | Y
data Axes3D = Axes2D | Z
the Axes2D in the Axes3D type is a value constructor taking no arguments, so Axes3D has two constructors, Axes2D and Z.
Different types cannot have value constructors with the same name (in the same scope) because that would make type inference impossible. What would
foo X = True
foo _ = False
have as a type? (It's a bit different with parametric types, all Maybe a have value constructors with the same name, and that works. But that's because Maybe takes a type parameter, and the names are shared only among types constructed with the same (unary) type constructor. It doesn't work for nullary type constructors.)
You can do it with Generalized Algebraic Data Types. We can create a generic (GADT) type with data constructors that have type constraints. Then we can define specialized types (type aliases) that specifies the full type and thus limiting which constructors are allowed.
{-# LANGUAGE GADTs #-}
data Zero
data Succ a
data Axis a where
X :: Axis (Succ a)
Y :: Axis (Succ (Succ a))
Z :: Axis (Succ (Succ (Succ a)))
type Axis2D = Axis (Succ (Succ Zero))
type Axis3D = Axis (Succ (Succ (Succ Zero)))
Now, you are guaranteed to only have X and Y passed into a function that is defined to take an argument of Axis2D. The constructor Z fails to match the type of Axis2D.
Unfortunately, GADTs do not support automatic deriving, so you will need to provide your own instances, such as:
instance Show (Axis a) where
show X = "X"
show Y = "Y"
show Z = "Z"
instance Eq (Axis a) where
X == X = True
Y == Y = True
Z == Z = True
_ == _ = False

Resources