Haskell Custom Math Types and Classes

Is there a way to make an instance of a class return a value that is not of the type of the instance? For example, you want to return a Double value for the scalar product of two vectors:

-- data structure to contain a 3D point in space data Point3D = Point3D !Double !Double !Double deriving (Eq, Ord) instance Num Point3D where -- Multiplication, scalar == Dot product Point3D x1 y1 z1 * Point3D x2 y2 z2 = x1*x2 + y1*y2 + z1*z2 :: Double 

Also, is there a way to determine how operators work between functions of different types? For example, I would like to define Point3D xyz + Double a = Point3D (x + a) (y + a) (z + a)

+4
source share
3 answers

Numeric operations in Num typeclass are defined with type :: Num n => n -> n -> n , so both operands and the return value must be of the same type. It is not possible to modify an existing class, so your parameters must either define new operators, or hide the existing Num class and completely replace it with your own implementation.

To implement operators that can have different types of operands, you will need several language extensions.

 {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FunctionalDependencies #-} 

Instead of a Num class that spans + , - and * , it is more flexible to define different types of stacks for different operands, because when Point3D * Double makes sense, Point3D + Double usually does not. Start with Mul .

 class Mul abc | ab -> c where (|*|) :: a -> b -> c 

Without extensions in type classes, there is only one type parameter, but with MultiParamTypeClasses we can declare a class of type Mul for a combination of types a , b and c . Part by parameters | ab -> c | ab -> c is a "functional dependency", which in this case claims that type c depends on a and b . This means that if we have an instance like Mul Double Point3D Point3D , then the functional dependency states that we cannot have any other instances of Mul Double Point3D c , where c something other than Point3D , i.e. The return type of multiplication is always explicitly determined by the type of operands.

Here we implement instances for Mul :

 instance Mul Double Double Double where (|*|) = (*) instance Mul Point3D Double Point3D where Point3D xyz |*| a = Point3D (x*a) (y*a) (z*a) instance Mul Double Point3D Point3D where a |*| Point3D xyz = Point3D (x*a) (y*a) (z*a) 

This flexibility does not come without its reservations, though, because it will make type inference much more difficult for the compiler. For example, you cannot just write

 p = Point3D 1 2 3 |*| 5 

Because the letter 5 not necessarily Double . It can be any Num n => n , and it is quite possible that someone announces new instances of the Mul Point3D Int Int , which behaves in a completely different way. So this means that we must explicitly specify the types of numeric literals.

 p = Point3D 1 2 3 |*| (5 :: Double) 

Now, if instead of defining new operands we want to override the default Num class from Prelude , we can do it like this

 import Prelude hiding (Num(..)) import qualified Prelude as P class Mul abc | ab -> c where (*) :: a -> b -> c instance Mul Double Double Double where (*) = (P.*) instance Mul Point3D Double Point3D where Point3D xyz * a = Point3D (x*a) (y*a) (z*a) 
+6
source

There is no way to get the standard Num functions (including operators) to return a different type. * has type Num n => n -> n -> n , which means that n must be of the same type.

There is also no way for the standard Num function (e.g. + ) to work with arguments of two different types.

The usual solution to this problem is to create a new operator. Thus, you can create a scalar addition operator, for example |+| , and use it to add doubles to your points.

If you are not opposed to Unicode, you can use ยท for your dot product :). Haskell supports this, but it may be difficult for other programs to type unicode.

+5
source

You can create your own class with multiplication, which can take on different types.

 import Prelude hiding ((*)) import qualified Prelude class Mul abc | ab -> c where (*) :: a -> b -> c instance Mul Double Double Double where (*) = (Prelude.*) instance Mul Double Int Double where a * b = a Prelude.* fromIntegral b ... 

To do this, you need to include classes with several parameters and functional dependencies.

+4
source

Source: https://habr.com/ru/post/1397710/


All Articles