I'm trying to create a polyvariadic function in Haskell, I used this answer to create a basic function. Here is the function's code :
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
But the problem is : when the function is called without any arguments, it does not work.
Couldn't match expected type 'Integer' with actual type 'Integer -> r0'
Probable cause: 'sumOf' is applied to too few arguments
For example, I would like to be able to write sumOf :: Integer
and have this function return 0
.
How should I do this ?
The simplest version only works for Integer
results.
This works off what you already wrote, taking advantage of the fact that 0
is the identity for addition.
class SumRes r where
sumOf' :: Integer -> r
instance SumRes Integer where
sumOf' = toInteger
instance (Integral b, SumRes r) => SumRes (b -> r) where
sumOf' a b = sumOf' $! a + toInteger b
sumOf :: SumRes r => r
sumOf = sumOf' 0
The two instances, Integer
and b -> r
, inherently don't overlap.
To get more general result types, you need a somewhat different approach, because the two instances described above mush together if Integer
is replaced by a type variable. You can do this with MultiParamTypeClasses
and TypeFamilies
.
{-# LANGUAGE ScopedTypeVariables, AllowAmbiguousTypes, DataKinds,
KindSignatures, TypeApplications, MultiParamTypeClasses,
TypeFamilies, FlexibleInstances #-}
module SumRes2 where
data Nat = Z | S Nat
class SumRes (c :: Nat) r where
sumOf' :: Integer -> r
type family CountArgs a :: Nat where
CountArgs (_ -> r) = 'S (CountArgs r)
CountArgs _ = 'Z
instance Num r => SumRes 'Z r where
sumOf' = fromInteger
instance (Integral b, SumRes n r) => SumRes ('S n) (b -> r) where
sumOf' a b = sumOf' @n (a + toInteger b)
sumOf :: forall r n. (SumRes n r, CountArgs r ~ n) => r
sumOf = sumOf' @n 0
The only limitation is that if you have an Integral
instance for a function type, you can't use sumOf
to produce it. That shouldn't really be a problem though. I've used TypeApplications
and AllowAmbiguousTypes
for brevity, but you can certainly use proxy passing or Tagged
instead.
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