2
votes

I'm a complete newbie in Haskell so please be patient.

Let's say I've got this class

class Indexable i where
  at :: i a p -> p -> a

Now let's say I want to implement that typeclass for this data type:

data Test a p = Test [a]

What I tried is:

instance Indexable Test where
    at (Test l) p = l `genericIndex` p

However it didn't compile, because p needs to be an Integral, however as far as I understand, it's impossibile to add the type signature to instances. I tried to use InstanceSigs, but failed.

Any ideas?

4
maybe the family/constraint kind stuff is a bit heavy - why don't you start with a multi-param typeclass and add the index type? then you can constraint the index type for your instanceRandom Dev

4 Answers

4
votes

here is a version where you add the index-type to the class using MultiParamTypeClasses

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}

module Index where

import Data.List (genericIndex)

class Indexable i f  where
  at :: forall a . f a -> i -> a

data Test a = Test [a]

instance Integral i => Indexable i Test where
  at (Test as) i = as `genericIndex` i

here I need the FlexibleInstances because of the way the instance is declared and RankNTypes for the forall a . ;)

assuming this is your expected behavior:

λ> let test = Test [1..5]
λ> test `at` 3
4
λ> test `at` 0
1
λ> test `at` (0 :: Int)
1
λ> test `at` (1 :: Integer)
2
2
votes

Just for fun, here's a very different solution which doesn't require any changes to your class declaration. (N.B. This answer is for fun only! I do not advocate keeping your class as-is; it seems a strange class definition to me.) The idea here is to push the burden of proof off from the class instance to the person constructing a value of type Test p a; we will demand that constructing such a value will require an Integral p instance in scope.

All this code stays exactly the same (but with a new extension turned on):

{-# LANGUAGE GADTs #-}
import Data.List

class Indexable i where
    at :: i a p -> p -> a
instance Indexable Test where
    at (Test l) p = l `genericIndex` p

But the declaration of your data type changes just slightly to demand an Integral p instance:

data Test a p where
    Test :: Integral p => [a] -> Test a p
2
votes

You are actually trying to do something fairly advanced. If I understand what you want, you actually need a multiparameter typeclass here, because your type parameter "p" depends on "i": for a list indexed by integer you need "p" to be integral, but for a table indexed by strings you need it to be "String", or at least an instance of "Ord".

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}   -- Enable the language extensions.

class Indexable i p | i -> p where
   at :: i a -> p -> a

This says that the class is for two types, "i" and "p", and if you know "i" then "p" follows automatically. So if "i" is a list the "p" has to be Int, and if "i" is a "Map String a" then "p" has to be "String".

instance Indexable [a] Int where
   at = (!!)

This declares the combination of [a] and Int as being an instance of Indexable.

user2407038 has provided an alternative approach using "type families", which is a more recent and sophisticated version of multiparameter type classes.

1
votes

You can use associated type families and constraint kinds:

import GHC.Exts(Constraint)

class Indexable i where
  type IndexableCtr i :: * -> Constraint 
  at :: IndexableCtr i p => i a p -> p -> a

instance Indexable Test where 
  type IndexableCtr Test = Integral 
  at (Test l) p = l `genericIndex` p

This defines the class Indexable with an associated type IndexableCtr which is used to constraint the type of at.