Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Top-level OverloadedLists literal

I've got a test suite for a refactoring exercise where I'd like it to be compatible with both Data.List and Data.List.NonEmpty. The exercise consists of a function foo :: [Foo] -> Foo and the test suite has some

data Case = Case
  { description :: String
  , input :: [Foo]
  , expected :: Foo
  }

cases :: [Case]
cases =
  [ Case { description = "blah blah"
         , input = [Foo 1, Foo 2, Foo 3]
         , expected = Foo 1
         }
  , ...
  ]

To make the test suite polymorphic with OverloadedLists, I tried

{-# LANGUAGE OverloadedLists #-}
...

data Case list = Case
  { description :: String
  , input :: list Foo
  , expected :: Foo
  }

cases =
  [ Case { description = "blah blah"
         , input = [Foo 1, Foo 2, Foo 3]
         , expected = Foo 1
         }
  , ...
  ]

but this gives me the error

    • Couldn't match expected type ‘GHC.Exts.Item (list0 Foo)’
                  with actual type ‘Foo’
      The type variable ‘list0’ is ambiguous
      ...
   |
50 |          , input = [Foo 1, Foo 2, Foo 3]
   |                     ^^^^^

I thought to move the IsList list constraint to the Case data type, like so

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedLists #-}
...

data Case list where
  Case :: IsList list => String -> list Foo -> Foo -> Case list

cases =
  [ Case "blah blah" [Foo 1, Foo 2, Foo 3] (Foo 1), ... ]

but this gives me the error

    • Expected kind ‘* -> *’, but ‘list’ has kind ‘*’
    • In the type ‘list Foo’
      In the definition of data constructor ‘Case’
      In the data declaration for ‘Case’
   |        
24 |   Case :: IsList list => String -> list Foo -> Foo -> Case list
   |                                    ^^^^^^^^

I'm not sure what the simplest approach here is. Any hints?

like image 465
sshine Avatar asked Dec 04 '19 10:12

sshine


1 Answers

The reason this does not work is because the Item type of a List (l Foo) => l is not per se Foo. The extension makes abstraction of that, and thus it expects the elements of your list literal to be of type Item (l Foo).

You can however add a type constraint that says that the items are indeed of type Foo:

{-# LANGUAGE OverloadedLists #-}

data Case list = Case
  { description :: String
  , input :: list Foo
  , expected :: Foo
  }

cases :: (IsList (l Foo), Item (l Foo) ~ Foo) => [Case l]
cases = [
    Case { description = "blah blah"
         , input = [Foo 1, Foo 2, Foo 3]
         , expected = Foo 1
         }
  ]

Here we thus say that the Item (l Foo) should be the same as Foo.

like image 68
Willem Van Onsem Avatar answered Oct 08 '22 11:10

Willem Van Onsem