4

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 ?

Artémis Young
  • 3,122
  • 1
  • 12
  • 17
  • 1
    There are no zero-argument functions in Haskell. You can change the instance for `Integer` to `sumOf = const 0`, but you still need to provide some argument when you call it. If you instead change `sumOf :: r`, you can change the `Integer` instance to `sumOf = 0`, but that will mean a much bigger change to your `a -> r` instance. – chepner Sep 06 '17 at 15:43
  • (It's not clear how `a -> r` is supposed to work. `x` should be a value of type `a -> r`, but you treat it as a value of type `Num a => a`. – chepner Sep 06 '17 at 15:44
  • 1
    Why do you want to do this? What's wrong with `sumOf :: [Integer] -> Integer`? – Benjamin Hodgson Sep 06 '17 at 23:35
  • @chepner yes that was my main problem : a function without arguments is not possible – Artémis Young Sep 07 '17 at 06:43
  • @BenjaminHodgson , just for personnal projects and to push my Haskell's knowledge a bit further :) – Artémis Young Sep 07 '17 at 06:44

1 Answers1

7

The simplest version only works for Integer results.

The easy way

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.

The harder way

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.

dfeuer
  • 48,079
  • 5
  • 63
  • 167