Top-level OverloadedLists literal
Asked Answered
F

1

6

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?

Full answered 4/12, 2019 at 10:7 Comment(2)
It should be Case :: IsList (list Foo) => String -> list Foo -> Foo -> Case list, since IsList takes a type with kind *, not * -> *..Cornered
But this will not fix the underlying problem, since the items of such overloaded lists are of type Item, and that is, although very common, not per se Foo.Cornered
C
9

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.

Cornered answered 4/12, 2019 at 10:23 Comment(1)
Ah, so this uncovered two misconceptions, one of which I was not aware. Thanks a bunch!Full

© 2022 - 2024 — McMap. All rights reserved.