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.