Search code examples
haskellfunctional-programming

Can one define a data type that wraps values of only those types for which an instance of a particular multiparameter type class exists?


Here is code illustrating the problem:

{-# LANGUAGE QuantifiedConstraints #-}

class SomeClass a b where
  someFunction :: a -> b -> a

data FirstInInstanceOfSomeClass = forall a. (forall b. SomeClass a b) => First a

instance SomeClass Bool String where
  someFunction = const

x = First True {-
               • No instance for ‘SomeClass Bool b’
                   arising from the head of a quantified constraint
                   arising from a use of ‘First’
               • In the expression: First True
                 In an equation for ‘x’: x = First Truetypecheck(-Wdeferred-
                 type-errors)
               -}

I want x to be a valid value since there is an instance of SomeClass for Bool, i.e., the one with String. Is there a way to achieve this?

Clarification: there might be many instances of SomeClass where Bool is in the first position. What I want to require is that there shall be at least one such instance for Bool so that FirstInInstanceOfSomeClass may wrap Bool values.

Edit: generally speaking, b does not functionally depend on a.


Solution

  • Your FirstInInstanceOfSomeClass doesn't make sense: you want a to be a parameter, not existential, and then b should be existential, not universal (as in (exists b. SomeClass a b) => First a, or equivalently forall b. SomeClass a b => First a by currying, which is the syntax Haskell supports).

    data FirstInInstanceOfSomeClass a = forall b. SomeClass a b => First a
    

    Of course, the compiler now complains that b is ambiguous in the type of First; one way to fix that if you have a functional dependency of b on a is to just declare it:

    {-# LANGUAGE FunctionalDependencies #-}
    
    class SomeClass a b | a -> b where
      someFunction :: a -> b -> a
    

    Otherwise you can use AllowAmbiguousTypes and TypeApplications:

    {-# LANGUAGE TypeApplications, AllowAmbiguousTypes #-}
    
    data FirstInInstanceOfSomeClass a = forall b. SomeClass a b => First a
    
    x = First @_ @String True
    

    Or Proxy:

    import Data.Proxy
    
    data FirstInInstanceOfSomeClass a = forall b. SomeClass a b => First (Proxy b) a
    
    x = First (Proxy :: Proxy String) True