2

I have two functions like these:

foo :: a -> b -> x -> x
bar :: c -> y -> y

I would like to unify them into single interface so they both could have same name. In my case it's guaranteed that types x and y are different (but there might be multiple x's). So it looks possible. But all my attempts have bad type inference in case of partial application :(

Could you help to come up with interface for this case?

My previous attempts look like this but this solution requires explicit type application to disambiguate cases:

class Insert s i where
  insert :: i

instance (Ord k, i ~ (k -> Set k -> Set k)) => Insert Set i where
  insert = Set.insert
10
  • Can you provide a bit more information? Why is it important that two functions with incompatible signatures share a name? What sort of code are you writing that abstracts over the interface? Commented Jul 22, 2018 at 6:02
  • @Carl I would like to unify these two functions: insert :: Ord k => k -> v -> Map k v -> Map k v and insert :: Ord a => a -> Set a -> Set a. Yes, I know that this might be a bad idea and that some people might not like it. But I don't force anyone to use this approach. I just interested whether it's possible or not. Commented Jul 22, 2018 at 6:12
  • 2
    That doesn't sound like a useful thing to do. You should create unified interfaces when it's possible to write meaningful code that doesn't care what instance of the interface it's using, not when names happen to be the same. This is true in all languages. If you don't have the ability to use different instantiations interchangeably, you don't have an abstraction, you have a coincidence. Commented Jul 22, 2018 at 6:18
  • 1
    @Carl It's possible to have separate names like insertMap and insertSet to separate use cases. But it's less convenient since historically name is insert for different types :( I don't think this is only coincidence because Set a can be just a special case of Map like Map a (). But it's not convenient to insert () manually. Commented Jul 22, 2018 at 7:05
  • 1
    If foo were uncurried to foo :: (a, b) -> x -> x, it would make more sense to unify foo and bar. Commented Jul 22, 2018 at 15:24

1 Answer 1

1

I guess the following probably works. As per the comments, it's still a terrible, terrible, terrible idea. If you really want to write Haskell like this, then you don't really want to write Haskell.

Anyway, the idea is to provide a type class for:

insert :: (Ord k) => k -> v -> a

parameterized in all three parameters with a functional dependency specifying that a determines k and v. In your case, a ~ (Map k' v' -> Map k' v') implies k ~ k' and v ~ v', while a ~ Set t implies k ~ t and v ~ Set t.

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies,
             NoMonomorphismRestriction #-}

module UnifiedInsert where

-- insert :: Ord k => k -> v -> Map k v -> Map k v
-- insert :: Ord a => a -> Set a -> Set a

import Data.Map (Map)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set

class Insert k v a | a -> k, a -> v where
  insert :: (Ord k) => k -> v -> a
instance Insert k (Set k) (Set k) where
  insert = Set.insert
instance Insert k v (Map k v -> Map k v) where
  insert = Map.insert

fromList = foldr insert mempty

foo1 :: Set Int
foo1 = fromList [1..10]

Note that the type checker can deduce the type of the function fromList (even if foo1 is dropped from the program), as long as the monomorphism restriction is off. It will have the rather preposterous type:

fromList :: (Foldable t, Insert k v v, Ord k, Monoid v) => t k -> v

which can, indeed, be specialized to Ord a => [a] -> Set a, as required.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.