Search code examples
haskelltypesghcgeneric-programming

Function type specialisation in Haskell


I have a function that has type Read a => String -> a, is it possible to have another function with the same name that does things differently when a is for example String? Are there any GHC extensions that allow this?

Something like:

f :: Read a => String -> a
f = read

f :: String -> String
f = id

Solution

  • In Haskell, this kind of function overloading (ad-hoc polymorphism) is accomplished by using type classes, not by binding the same name under multiple types.

    {-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
    
    class F a where f :: String -> a
    
    instance F String where f = id
    instance F Int where f = read
    instance F Char where f = read
    instance F Float where f = read
    -- etc.
    

    Now, f can operate on any type for which an instance of F has been declared.

    Unfortunately, you can't get away with the following:

    instance Read a => F a where f = read
    

    Perhaps unintuitively, this does not declare an instance of F only for types which have an instance of Read. Because GHC resolves instances using only the head of the instance declaration (the part to the right of the =>), this actually declares all types a to be instances of F, but makes it a type error to call f on anything which is not also an instance of Read.

    It will compile if you enable the UndecidableInstances extension, but this just leads to other problems. It's a rabbit hole you don't really want to venture down.

    Instead, you should declare an instance of F for each individual type you intend f to operate on. This isn't very burdensome for a simple class like this one, but if you use a recent version of GHC, you can use the following to make it slightly easier:

    {-# LANGUAGE DefaultSignatures #-}
    
    class F a where f :: String -> a
                    default f :: Read a => String -> a
                    f = read
    

    Now, for any type which is an instance of Read, you may declare its instance of F without having to provide the implementation of f explicitly:

    instance F Int
    instance F Char
    instance F Float
    -- etc.
    

    For any types without instances of Read, you'll still have to write an explicit implementation for f.