V
V
Vasily Demin2020-08-25 21:14:04
Haskell
Vasily Demin, 2020-08-25 21:14:04

How to declare a type class with two parameters so that the compiler understands the type of the first from the type of the second?

There is a type class declared like this:

newtype Fix f = Fix { unFix :: f (Fix f) }

class Functor f => Fixable t f where
    toFix :: t -> Fix f
    fromFix :: Fix f -> t


The following data types are instances of it:
-- Сам тип Fix
instance Functor f => Fixable (Fix f) f where
    toFix = id
    fromFix = id

-- И список
data ListF a b = Nil | Cons a b deriving (Eq, Functor)

instance Fixable [a] (ListF a) where
    toFix = apo go
      where go (x:xs) = Cons x (Right xs)
            go _ = Nil
    fromFix = cata go
      where go (Cons x xs) = x:xs
            go _ = []


An error occurs in this piece of code:
main :: IO ()
main = putStrLn $ someFunc [\a b -> a ++ b, \a b -> a ++ b ++ a] "str1" "str2"
  where someFunc cs = para go cs


It is clear from the context that cs is a list of type functions: But the compiler infers a too general type, namely:
String -> String -> String

someFunc :: Fixable t (ListF (String -> String -> String)) -> (String -> String -> String)

In this connection, a typing error occurs, and the compiler requires the use of the FlexibleContexts extension. If you specify the function type explicitly:
someFunc :: [String -> String -> String] -> (String -> String -> String)

the code compiles without errors. How can I make the compiler infer the correct type?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
W
wiz, 2020-09-01
@includedlibrary

There are two ways - classical via FunctionalDependencies and neoclassical via TypeFamilies .
Functional dependencies are made specifically for classes with multiple parameters:

{-# LANGUAGE FunctionalDependencies #-}
class Collects e ce | ce -> e where
  empty  :: ce
  insert :: e -> ce -> ce
  member :: e -> ce -> Bool
  toList :: ce -> [e]

usage :: Collects e ce => ce -> Maybe e

Type families are more general and can work separately from classes. But within classes, so-called "associated types" can be defined. Then the class will not have this parameter, but the instance will have to provide a function from itself to the associated type:
{-# LANGUAGE TypeFamilies #-}
class Collects ce where
  type Elem ce
  empty  :: ce
  insert :: Elem ce -> ce -> ce
  member :: Elem ce -> ce -> Bool
  toList :: ce -> [Elem ce]

usage :: Collects ce => ce -> Maybe (Elem ce)

More details are on the wiki .

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question