Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polyvariadic generalised sum

This answer demonstrates a polyvariadic function that sums its arguments:

class SumRes r where 
    sumOf :: Integer -> r

instance SumRes Integer where
    sumOf = id

instance (Integral a, SumRes r) => SumRes (a -> r) where
    sumOf x = sumOf . (x +) . toInteger

I have created a generalised version of this function for all members of Num:

class (Num n) => MySumType n r where
    mySum :: n -> r

instance (Num n) => MySumType n n where
    mySum x = x

instance (Num n, MySumType n r) => MySumType n (n->r) where
    mySum x = mySum . (x +)

However this only works with calls like mySum (1::Int) (3::Int) (2::Int) :: Int. Without the type specifier on the arguments, I get this error:

No instance for (MySumType n0 Float) arising from a use of `mySum'

Possible fix: add an instance declaration for (MySumType n0 Float)

In the expression: mySum 1.0 :: Float

In an equation for `it': it = mySum 1.0 :: Float

What is causing this problem and how can I fix it? I suspect it is something to do with the type of integer literals being Num a => a. Also is there an equivalent function to the one above that does not rely on extensions? The ones used above are multiple parameter type classes and flexible instances.

like image 1000
Dylan Avatar asked Apr 30 '13 19:04

Dylan


1 Answers

I have~ not come across a compelling use case for polyvariadic functions in haskell, as of yet, that could not be solved by using a list or similar data structure. So if you think you have one beyond novelty, the reason I have played around with them, I would enjoy knowing what it is. Enough examples have been given below, some of which I should have thought of while making the comment, that I have retracted my statement.

{-# language MultiParamTypeClasses #-}
{-# language FlexibleInstances #-}
{-# language TypeFamilies #-}
{-# language IncoherentInstances #-}

class (Num n) => MySumType n r where
    mySum :: n -> r

instance (Num n, m~n) => MySumType n m where
    mySum x = x

instance (Num n, MySumType n r, n~m) => MySumType n (m->r) where
    mySum x = mySum . (x +)

Then after loading the file into ghci:

> mySum 1 2 4 5 6 7 :: Int
25
> mySum 1.1 2 4.6 5 6.9 7 :: Double
26.6

Type inference can also be your friend in some cases allowing you to drop the finial type annotation like in the following contrived case:

> replicate (mySum 1 2 3 4) 6
[6,6,6,6,6,6,6,6,6,6]

Regarding:

Also is there an equivalent function to the one above that does not rely on extensions?

I think you are out of luck. I would like to note that unless you have a reason to move away from GHC or stay with Haskell98 or Haskell2010 the extensions do you no harm and cause little compatibility problems since most people seem to be on GHC anyways.

Edit: Additional explanation

Let start off explaining the difference between the simpler instances. I will be adding the postfix 2 to some of the names for implementation I provided.

instance (Num n) => MySumType n n where
    mySum x = x

If you combine this with the class declaration:

class (Num n) => MySumType n r where
    mySum :: n -> r

mySum has a type signature of mySum :: (Num n) => n -> n. That n -> n is saying a 1 arity function that takes a type n, produces an n and n has a class of Num.

When using this mySum I have to specify what I give it and what it produces.

mySum 1 :: Int
mySum (1 :: Int)

will both give errors only when the input and the output type are specified will it give a result:

mySum (1 :: Int) :: Int
            ^       ^
            |       specify output type
            specify input type

produces a result( 1 in this case). This is because you have given an instance for n -> n but some one could later add a instance for n -> m such as Int -> Double like the following:

instance MySumType Int Double where
    mySum x = 2 * fromIntegral x

> mySum (1::Int) :: Int
1
> mySum (1::Int) :: Double
2.0

These instances each match a very narrow swath of all possible 1 arity function types.

Now lets look at the modified version

instance (Num n, m~n) => MySumType n m where
    mySum x = x

mySum here has the type signature mySum :: (Num n, m~n) => n -> m this type signature is saying for all 1 arity functions that take type n and produce type m where n has the class Num and m is equal to n. Notice that this started off matching all 1 arity functions, n -> m, any n to any m then put contrants on it.

Now it is possible to do:

> mySum2 (1::Int)
1
> mySum2 1 :: Int
1

Once the input is specified the output is specified too. Other explanations 1, 2.

This does not stop us from specifiy more specific instances like Int -> Double like before either:

instance MySumType Int Double where
    mySum x = 2 * fromIntegral x

> mySum2 1 :: Int
1
> mySum2 1 :: Double
1.0
> mySum2 (1 :: Int) :: Double
2.0

Only the last mySum2 is working off the Int -> Double instance. This is a property of IncoherentInstances and I think I will leave it to another stackoverflow question to answer the role that IncoherentInstances is playing.

like image 131
Davorak Avatar answered Oct 04 '22 20:10

Davorak