In Haskell, how can one overload a built in function such as !!
?
I originally was trying to figure out how to overload the built in function !!
to support by own data types. Specifically, !!
is of the type:
[a] -> Int -> a
and I want to preserve it's existing functionality, but also be able to call it where its type signature looks more like
MyType1 -> MyType2 -> MyType3
I originally wanted to do this because MyType1 is like a list, and I wanted to use the !!
operator because my operation is very similar to selecting an item from a list.
If I was overloading something like +
I could just add an instance of my function to the applicable type class, but I don't think that is an option here.
I'm not convinced I actually even want to overload this function anymore, but I am still interested in how it would be done. Actually, comments on if overloading an operator such as !!
is even a good idea would be appreciated as well.
You can't actually overload an existing non-typeclass function in Haskell.
What you can do is define a new function in a new type class, which is general enough to encompass both the original function and the new definition you want as an overload. You can give it the same name as the standard function, and avoid importing the standard one. That means in your module you can use the name !!
to get both the functionality of your new definition, and the original definition (the resolution will be directed by the types).
Example:
{-# LANGUAGE TypeFamilies #-}
import Prelude hiding ((!!))
import qualified Prelude
class Indexable a where
type Index a
type Elem a
(!!) :: a -> Index a -> Elem a
instance Indexable [a] where
type Index [a] = Int
type Elem [a] = a
(!!) = (Prelude.!!)
newtype MyType1 = MyType1 String
deriving Show
newtype MyType2 = MyType2 Int
deriving Show
newtype MyType3 = MyType3 Char
deriving Show
instance Indexable MyType1 where
type Index MyType1 = MyType2
type Elem MyType1 = MyType3
MyType1 cs !! MyType2 i = MyType3 $ cs !! i
(I've used type families to imply that for a given type that can be indexed, the type of the indices and the type of the elements automatically follows; this could of course be done differently, but going into that in more detail is getting side-tracked from the overload question)
Then:
*Main> :t (!!)
(!!) :: Indexable a => a -> Index a -> Elem a
*Main> :t ([] !!)
([] !!) :: Int -> a
*Main> :t (MyType1 "" !!)
(MyType1 "" !!) :: MyType2 -> MyType3
*Main> [0, 1, 2, 3, 4] !! 2
2
*Main> MyType1 "abcdefg" !! MyType2 3
MyType3 'd'
It should be emphasised that this has done absolutely nothing to the existing !!
function defined in the prelude, nor to any other module that uses it. The !!
defined here is a new and entirely unrelated function, which just happens to have the same name and to delegate to Prelude.!!
in one particular instance. No existing code will be able to start using !!
on MyType1
without modification (though other modules you can change can of course import your new !!
to get this functionality). Any code that imports this module will either have to module-qualify all uses of !!
or else use the same import Prelude hiding ((!!))
line to hide the original one.
In Haskell, nearly all operators are library-defined. Many of the ones you use the most are defined in the 'standard library' of the Prelude module that is imported by default. Gabriel's answer shows how to avoid importing some of those definitions so you can make your own.
That's not overloading though, because the operator still just means one thing; the new meaning you define for it. The primary method that Haskell provides for overloading, i.e. using an operator in such a way that it has different implementations for different types, is the type class mechanism.
A type class identifies a group of types that support some common functions. When you use those functions with a type, Haskell figures out the correct instance of the type class that applies to your usage and makes sure the correct implementation of the functions is used. Most type classes have just a few functions, some just one or two, that need to be implemented to make a new instance. Many of them offer a lot of secondary functions implemented in terms of the core ones as well, and you can use all of them with a type you make an instance of the class.
It so happens that others have made types that behave quite a bit like lists, and so there's already a type class called ListLike
. I'm not sure exactly how close your type is to a list, so it may not be a perfect fit for ListLike, but you should look at it as it will give you a lot of capability if you can make your type a ListLike instance.
Hide the Prelude's (!!)
operator and you can define your own (!!)
operator:
import Prelude hiding ((!!))
(!!) :: MyType1 -> MyType2 -> MyType3
x !! i = ... -- Go wild!
You can even make a type class for your new (!!)
operator if you prefer.