ryan91
ryan91

Reputation: 164

Call a function inside a class without declaring an instance

I want to create a function in Haskell that does different things depending the data type it is given. I thought classes should do what I want, but now I ran into a problem. What I would like to be able to do is something similar to:

let x1 = myFunction :: MyInstance1
let x2 = myFunction :: MyInstance2

and it does different things depending on the given instance.

My current approach is

class MyClass a where
    create :: Int -> a
    doSomething :: a -> [Int]

    myFunction :: [Int]
    myFunction = doSomething $ create 4

instance MyClass MyInstance1 where
    -- implementation of create and doSomething

instance MyClass MyInstance2 where
    -- implementation of create and doSomething

However, the compiler tells me "The type variable a0 is ambiguous in the ambiguity check for 'myFunction'" and from what I've been reading this is related to the compiler not knowing what instance of 'doSomething' to call.

So is there a way to call 'doSomething' in a "generic" way and enforce the data type later? Or do I need an entirely different approach for my problem?

--- EDIT ---

So I applied chi's answer to my problem, but it does not solve it completely yet. Here's my code

{-# LANGUAGE AllowAmbiguousTypes #-}

class C a where
  myFunction :: Int
  create :: Int -> a
  doSomething :: a -> Int
  -- anotherFunction :: Int -> Int
  -- anotherFunction x = doSomething $ create 4

instance C Int where
  myFunction = 1
  create x = 2 * x
  doSomething x = x + 4

instance C Bool where
  myFunction = 2
  create x = True
  doSomething x = if x then 42 else 24

This compiles and I in the prompt

create @ Bool 4
create @ Int 4

returns the expected results. However, anotherFunction does not compile properly giving the error message

Test.hs:8:23: error:
    • Could not deduce (C a0) arising from a use of ‘doSomething’
      from the context: C a
        bound by the class declaration for ‘C’ at Test.hs:(3,1)-(8,44)
      The type variable ‘a0’ is ambiguous
      These potential instances exist:
        instance C Bool -- Defined at Test.hs:15:10
        instance C Int -- Defined at Test.hs:10:10
    • In the expression: doSomething $ create 4
      In an equation for ‘anotherFunction’:
          anotherFunction x = doSomething $ create 4
Failed, modules loaded: none.

Is it simply not possible to use doSomething in this context? My idea is to implement the function in the same manner for all instances and then write

anotherFunction @ Bool 4
anotherFunction @ Int 6

Upvotes: 3

Views: 438

Answers (1)

chi
chi

Reputation: 116139

You need a couple of extensions to do that, but it is doable. Here's a GHCi session showing that:

> :set -XAllowAmbiguousTypes
> class C a where myFunction :: Int
> instance C Int where myFunction = 1
> instance C Bool where myFunction = 2
> :set -XTypeApplications
> myFunction @ Int
1
> myFunction @ Bool
2

An "old" solution would be to add a proxy argument

class C a where myFunction :: proxy a -> Int

but hopefully this will fade out of style in a few years -- I find passing types explicitly clearer than passing proxies.


Full code, with another example:

{-# LANGUAGE AllowAmbiguousTypes, TypeApplications, ScopedTypeVariables #-}

class C a where
  myFunction :: Int
  create :: Int -> a
  doSomething :: a -> Int
  anotherFunction :: Int -> Int
  anotherFunction x = doSomething $ create @ a 4

instance C Int where
  myFunction = 1
  create x = 2 * x
  doSomething x = x + 4

instance C Bool where
  myFunction = 2
  create x = True
  doSomething x = if x then 42 else 24

Tests:

> :set -XTypeApplications
> anotherFunction @ Bool 4
42
> anotherFunction @ Int 6
12

Upvotes: 4

Related Questions