List of Functors
haskell
Solution
In Haskell, downcasting is not allowed. You can use `AnyFunctor`, but the trouble with that is there is no longer any way to get back to a functor that you know. When you have an `AnyFunctor a`, all you know is that you have an `f a` for some `f`, so all you can do is `fmap` (getting you another `AnyFunctor`). Thus, `AnyFunctor a` is in fact equivalent to `()`.
You can add structure to `AnyFunctor` to make it more useful, and we'll see a bit of that later on.
Functor Coproducts
But first, I'll share the way that I would probably end up doing this in a real program: by using functor combinators.
{-# LANGUAGE TypeOperators #-}
infixl 1 :+: -- declare this to be a left-associative operator
data (f :+: g) a = FLeft (f a) | FRight (g a)
instance (Functor f, Functor g) => Functor (f :+: g) where
-- left as an exercise
As the data type reads, `f :+: g` is a functor whose values can be either `f a` or `g a`.
Then you can use, for example:
l :: [ (Maybe :+: []) Int ]
l = [ FLeft (Just 1), FRight [2,3,4], FLeft Nothing ]
And you can observe by pattern matching:
getMaybe :: (Maybe :+: g) a -> Maybe a
getMaybe (FLeft v) = v
getMaybe (FRight _) = Nothing
It gets ugly as you add more functors:
l :: [ (Maybe :+: [] :+: Either Int) Int ]
l = [ FLeft (FLeft Nothing), FRight (Right 42) ]
-- Remember that we declared :+: left-associative.
But I recommend it as long as you can handle the ugliness, because it tracks the list of possible functors in the type, which is an advantage. (Perhaps you eventually need more structure beyond what `Functor` can provide; as long as you can provide it for `(:+:)`, you're in good territory.)
You can make the terms a bit cleaner by creating an explicit union, as Ganesh recommends:
data MyFunctors a
= FMaybe (Maybe a)
| FList [a]
| FEitherInt (Either Int a)
| ...
But you pay by having to re-implement `Functor` for it (`{-# LANGUAGE DeriveFunctor #-}` can help). I prefer to put up with the ugliness, and work at a high enough level of abstraction where it doesn't get too ugly (i.e. once you start writing `FLeft (FLeft ...)` it's time to refactor & generalize).
Coproduct can be found in the comonad-transformers package if you don't want to implement it yourself (it's good exercise though). Other common functor combinators are in the `Data.Functor.` namespace in the transformers package.
Existentials with Downcasting
`AnyFunctor` can also be extended to allow downcasting. Downcasting must be explicitly enabled by adding the `Typeable` class to whatever you intend to downcast. Every concrete type is an instance of `Typeable`; type constructors are instances of `Typeable1` (1 argument); etc. But it doesn't come for free on type variables, so you need to add class constraints. So the `AnyFunctor` solution becomes:
{-# LANGUAGE GADTs #-}
import Data.Typeable
data AnyFunctor a where
AnyFunctor :: (Functor f, Typeable1 f) => f a -> AnyFunctor a
instance Functor AnyFunctor where
fmap f (AnyFunctor v) = AnyFunctor (fmap f v)
Which allows downcasting:
downcast :: (Typeable1 f, Typeable a) => AnyFunctor a -> Maybe (f a)
downcast (AnyFunctor f) = cast f
This solution is actually cleaner than I had expected to be, and may be worth pursuing.
Problem
This might apply for any type class, but lets do it for Functors as I know them better. I wan't to construct this list. ``` l = [Just 1, [1,2,3], Nothing, Right 4] ``` and then ``` map (fmap (+1)) l ``` to get ``` [Just 2, [2,3,4], Nothing, Right 5] ``` I know they are all Functors that contain Ints so it might be possible. How can I do this? Edit This is turning out to be messier than it would seem. In Java or C# you'd declare the `IFunctor` interface and then just write ``` List<IFunctor> l = new List<IFunctor> () { new Just (1), new List<Int>() {1,2,3}, new Nothing<Int>(), new Right (5) } ``` assuming `Maybe`, `List` and `Either` implement the `IFunctor`. Naturally `Just` and `Nothing` extend `Maybe` and `Right` and `Left` extend `Either`. Not satisfied with this problem being easier to resolve on these languages!!! There should cleaner way in Haskell :(