Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Associate a value with a type in Haskell using a typeclass

Tags:

haskell

I want to use a typeclass to return a String instance that is functionally dependent upon a Haskell type. For example, imagine we have the type Form. I want to associate the string "form" with this type. Given the type Invocation, I want to associate the string "job". And so on. The important thing is that I will often not have an instance of the type in question, though the type will be in the type signature of any function that uses it.

Perhaps an easy way to demonstrate this is with Swift:

protocol Resource {
  static var resourcePathSegment: String { get }
}

struct Form : Resource {
    static let resourcePathSegment = "form"
}

struct Invocation: Resource {
    static let resourcePathSegment = "job"
}

print(Form.resourcePathSegment)

Why would I want such a thing? I'm talking to a REST API that uses certain conventions. For a GET request, the path segment to the resource in question is functionally dependent upon the type of the data returned from the request. However, I won't have an instance of this type until the request completes.

like image 774
Gregory Higley Avatar asked Feb 09 '19 06:02

Gregory Higley


2 Answers

Using TypeApplications, this is easy:

{-# LANGUAGE AllowAmbiguousTypes, TypeApplications #-}

class Resource a where
  resourcePathSegment :: String

instance Resource Form where
  resourcePathSegment = "form"

instance Resource Invocation where
  resourcePathSegment = "job"

To illustrate how to use the above:

ghci> :t resourcePathSegment
resourcePathSegment :: Resource a => String
ghci> resourcePathSegment @Form
"form"
ghci> resourcePathSegment @Invocation
"job"
like image 129
Alexis King Avatar answered Nov 15 '22 07:11

Alexis King


The Proxy type, from Data.Proxy, is defined as data Proxy a = Proxy. This may be used to create a typeclass for this functionality:

class Resource a where
    name :: Proxy a -> String

instance Resource Form
    name _ = "form"

instance Resource Invocation where
    name _ = "job"

-- etc.

You can then get the name of a type by calling name (Proxy :: Proxy type_name).

There are several variations of this technique; an excellent summary can be found at the recent blog post Proxy arguments in class methods: a comparative analysis.

EDIT: I see that @Alexis King has submitted a very similar answer. Refer to the blog post above for an overview of the pros and cons of each approach. (Personally, I would use Alexis's technique, since I like TypeApplications, but I feel that mine is a bit simpler since it doesn't require AllowAmbiguousTypes or TypeApplications to use.)

like image 37
bradrn Avatar answered Nov 15 '22 07:11

bradrn