Search code examples
purescript

How declare tagged union of polymorphic collection types


I'm new to Purescript. My current learning exercise is to create a tagged union of polymorphic Array and List. I'll use it in a function that finds the length of any Array or List. Here's my attempt:

import Data.List as L
import Data.Array as A

data Collection = CollectionList (forall a. L.List a) 
                | CollectionArray (forall b.  Array b)

colLength :: Collection -> Int
colLength (CollectionList list) = L.length list
colLength (CollectionArray arr) = A.length arr

main :: Effect Unit
main = do
   logShow (colLength (CollectionArray [3,5]))

The compiler doesn't like it:

 Could not match type Int with type b0     

 while checking that type Int is at least as general as type b0
 while checking that expression 3 has type b0
 in value declaration main

 where b0 is a rigid type variable

I'm confused by the parts, checking that type Int is at least as general as type b0 and b0 is a rigid type variable. My intention was to allow b to be anything. Not sure what I did to make the compiler put conditions on what b can be.

If you know how, please show the correct way to define a tagged union of polymoric types that'll work in my colLength function.


Solution

  • forall a doesn't mean "any type goes here"

    It means that whoever accesses the value, gets to choose what a is, and whoever provides the value has to make sure that the value is of that type. It's a contract between the provider and the consumer.

    So when you provide the value CollectionArray [3,5], you have to make it such that it works for all possible a that whoever accesses that value later might choose.

    Obviously, there is only one way you can construct such value:

    CollectionArray []
    

    What you probably actually meant to do (and I'm guessing here) was to make your collection polymorphic, in the sense that it can contain values of any type, but the type is chosen by whoever creates the collection, and then whoever accesses it later has to deal with that particular type.

    To do that, you have to put the type variable on the outside:

    data Collection a = CollectionList (L.List a) 
                      | CollectionArray (Array a)
    

    That way, when you create a collection CollectionArray [3,5], it becomes of type Collection Int, and now everywhere you pass it, such as colLength, will have to deal with that Int

    This, in turn, can be achieved by making colLength itself generic:

    colLength :: forall a. Collection a -> Int
    colLength (CollectionList list) = L.length list
    colLength (CollectionArray arr) = A.length arr
    

    Now whoever accesses (i.e. calls) colLength itself gets to choose what a is, which works fine, because it's the same place that created the Connection Int in the first place.