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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With