Whitetip
Whitetip

Reputation: 43

Is there a wildcard type variable in Haskell?

I want to be able to polymorphically handle multiple Either values who share the same Left type, but not the same Right type, e.g.:

foo = Left "foo" :: Either String Int
bar = Left "bar" :: Either String Char
list = [foo, bar]

The Left value indicates an error message in case the computation failed, while the Right value has different types depending on different computations. In case of failure I only care about gathering the errors on the left, so it would be useful to be able to treat all of them the same for this purpose.

However, this fails, and the compiler expects both type variables to match:

    * Couldn't match type `Char' with `Int'
      Expected type: Either String Int
        Actual type: Either String Char
    * In the expression: bar
      In the expression: [foo, bar]
      In an equation for `list': list = [foo, bar]

After a lot of searching, the only thing I stumbled upon is something called a "Partial Type Signature", which uses the "_" wildcard, and has to be enabled explicitly by a flag for it to compile. However, even after enabling it, it still doesn't seem to change anything:

foo = Left "foo" :: Either String Int
bar = Left "bar" :: Either String Char
list = [foo, bar] :: Either String _

And I receive the same error:

    * Couldn't match type `Char' with `Int'
      Expected type: Either String Int
        Actual type: Either String Char
    * In the expression: bar
      In the expression: [foo, bar] :: Either String _
      In an equation for `list': list = [foo, bar] :: Either String _

In Java (if it had an Either type), I could simply do something like this:

List<Either<String, ?>> list = asList(foo, bar);

And it would have worked just fine, type safe and whatnot.

Is there a type variable wildcard in Haskell corresponding to ? in Java? If not, how do you deal with such cases where you don't care about a certain type variable and want to abstract it? Is there an alternative?

More details:

Realizing that I wasn't clear enough about my problem, I will elaborate:

What I'm trying to implement is a command line parser. It should receive the user's input (command line arguments) and return a Params value, where:

data Params = Params String Double Bool

For simplicity's sake I only included 3 values in the value constructor above, but actually there are many more.

Since there are many different parameters, each one can fail in its own way - the user might not provide the necessary argument, the provided argument is invalid, etc.

The parsing should succeed only if all of the arguments provided are valid. If any of them is invalid, then the entire computation should fail. So I thought about the following solution (again, it's simplified, so please ignore other issues like efficiency):

parse :: [String] -> Either [String] Params
parse args = if successful
             then Right (Params strParam doubleParam boolParam)
             else Left errors
    where successful = null errors
          (Right strParam) = eitherStr
          (Right doubleParam) = eitherDouble
          (Right boolParam) = eitherBool
          errors = lefts [eitherStr, eitherDouble, eitherBool]
          eitherStr = parseStr args
          eitherDouble = parseDouble args
          eitherBool = parseBool args

parseStr :: [String] -> Either String String
parseDouble :: [String] -> Either String Double
parseBool :: [String] -> Either String Bool

But it doesn't compile, since I can't handle eitherStr, eitherDouble and eitherBool as having the same type, which would have been possible if there was a wildcard type variable. Hence my question.

Under the current circumstances, I have to resort to something like this:

parse :: [String] -> Either [String] Params
parse args = if successful
             then Right (Params strParam doubleParam boolParam)
             else Left errors
    where successful = null errors
          (Right strParam) = eitherStr
          (Right doubleParam) = eitherDouble
          (Right boolParam) = eitherBool
          errors = concat [strErrors, doubleErrors, boolErrors]
          strErrors = getErrors eitherStr
          doubleErrors = getErrors eitherDouble
          boolErrors = getErrors eitherBool
          eitherStr = parseStr args
          eitherDouble = parseDouble args
          eitherBool = parseBool args
          getErrors = lefts . (:[])

parseStr :: [String] -> Either String String
parseDouble :: [String] -> Either String Double
parseBool :: [String] -> Either String Bool

Doable, but messier. Of course, it's possible that my solution is completely off track, and there's a better idiomatic functional solution, which I'll be glad to hear about.

It was said that Java has Object as a universal upper bound, so the comparison is misleading. But it seems to me that there can always be a theoretical upper bound, even if in the worst case that upper bound means a value which you can't use. Which is just fine for cases where you don't need it, such as mine.

After trying existential types:

Existential types are of no help to me. They define a new type that wraps the old one, so functions that accept Either won't accept the new type, and can't be reused. So instead of using the existing lefts function, I have to implement it myself for the new type, in addition to the hassle of defining a new type:

data SomeEither l = forall r. SomeEither (Either l r)
data Params = Params String Double Bool deriving (Show)

parse :: [String] -> Either [String] Params
parse args = if successful
             then Right (Params strParam doubleParam boolParam)
             else Left errors
    where successful = null errors
          (Right strParam) = eitherStr
          (Right doubleParam) = eitherDouble
          (Right boolParam) = eitherBool
          errors = lefts' [SomeEither eitherStr, SomeEither eitherDouble, SomeEither eitherBool]
          eitherStr = parseStr args
          eitherDouble = parseDouble args
          eitherBool = parseBool args

lefts' :: [SomeEither l] -> [l]
lefts' [] = []
lefts' ((SomeEither (Left l)):xs) = l:(lefts' xs)
lefts' (x:xs) = lefts' xs

parseStr :: [String] -> Either String String
parseDouble :: [String] -> Either String Double
parseBool :: [String] -> Either String Bool

The whole point was to do less work and have cleaner code. The use of existential types in this case does the opposite.

Upvotes: 4

Views: 1182

Answers (2)

HTNW
HTNW

Reputation: 29193

This is fruitless. Either L R where R is unknown is just Maybe L. If you have Right (r :: R) and put it into your list, thereby forgetting what R is, then even though you can later take that Right r out of the list, you will not know the type of r. That makes it unusable. The same is basically true in Java: a Either<L, ?> is really just an Optional<L>. If the Either is a Right, well, all you get is an Object (the upper bound of the wildcard). Ignoring the "universal" Object operations in Java (most importantly instanceof and ==, but also equals, hashCode, wait, synchronized, etc.), none of which exist in Haskell (because Haskell values lack identity and runtime type), there's nothing you can do with this featureless blob. Why keep it in the first place?

Now, as to syntax: _ wildcards in Haskell types are markers that you don't know what type goes there and you want GHC to infer it. I could write:

something :: [a] -> _
something = foldr (:) []

and GHC will tell me I should fill in the _ with [a], because that's the type of something. The _ doesn't allow for anything "new"; it's just a convenience for when you don't know what the type of something is but you want to write it out for clarity (or when you don't want to write out the type because it's big, but that's rarer). In your case, [foo, bar] is ill-typed, period. Lists must be homogenous (contain elements of the same type) and [foo, bar] simply is not valid, and asking GHC to tell you what the type is not going to help you when there is no type in the first place.

The equivalent to Java's ? types are existential types:

-- SomeEither l = Either<L, ?>
data SomeEither l = forall r. SomeEither (Either l r)

A SomeEither l (declared on the LHS) value is (according to the RHS) a pair, consisting of a type r :: Type and a value of type Either l r. This is also the meaning of Either<L, ?> in Java: Either<L, ?> is the type of pairs consisting of a type R (abusing the Java jargon, R is the "capture type") and a value Either<L, R>. However, in Java, the packing and unpacking of these pairs is implicit (packing is called "upcasting" and unpacking is called "wildcard capture"). In Haskell, you'd have to say

list = [SomeEither foo, SomeEither bar]

but now we're back to my first point that now you can never use the Rights ever again:

forM_ list (\(SomeEither x) ->
    case x of
        Left l -> putStrLn l
        Right r -> putStrLn ":(") -- don't know type of r; can't really use it for anything!

So, if you're going to have to preprocess the values before they go into the list, and if there's no point keeping the Right values around, then just get rid of them at the beginning:

list :: [Maybe String]
list = [left foo, left bar]
  where left = either Just (const Nothing)
-- or maybe you just want
list :: [String]
list = catMaybes [left foo, left bar]
  where left = either Just (const Nothing)

EDIT: "Putting things in lists" is 100% not the solution to your edited question. Note that even if you had wildcard types like in Java, they wouldn't really help, either. I think you actually want this Applicative (which is almost WriterT [String] Maybe):

newtype Validated a = Validated { runValidated :: Either [String] a }
  deriving Functor
instance Applicative Validated where
  pure = Validated . Right
  Validated l <*> Validated r = Validated (l <!> r)
    where
      Left xs <!> Left ys = Left (xs ++ ys)
      Left xs <!> Right _ = Left xs
      Right _ <!> Left xs = Left xs
      Right f <!> Right xs = Right (f xs)

parseStr :: [String] -> Validated String
parseDouble :: [String] -> Validated Double
parseBool :: [String] -> Validated Bool
parse :: [String] -> Validated Params
parse args = Params <$> parseStr args <*> parseDouble args <*> parseBool args

(Sadly, it's not a Monad.) No erasing of types, nothing fancy. Just combining all the errors and producing a value when there are none.

Upvotes: 7

Mark Seemann
Mark Seemann

Reputation: 233150

New answer based on updated question

(Old answer below)

Based on the updated question, it sounds like a standard validation problem. This is essentially a FAQ, so I'll refer you to another answer of mine for more details.

You can use Applicative to collect error messages, but for various reasons, the Either instance isn't suitable. What you can do is wrap Either in a newtype with a more appropriate Applicative instance and use that instead:

newtype Validation e r =
  Validation { runValidation :: Either e r } deriving (Eq, Show, Functor)

instance Semigroup m => Applicative (Validation m) where
  pure = Validation . pure
  Validation (Left x) <*> Validation (Left y) = Validation (Left (x <> y))
  Validation f <*> Validation r = Validation (f <*> r)

It'll also make things easier for you if you change the individual parsing functions to return Validation values:

parseStr :: String -> Validation [String] String
parseDouble :: String -> Validation [String] Double
parseBool :: String -> Validation [String] Bool

But if you don't want to do that, you can always lift Either-returning functions to Validation afterwards.

Now you can easily compose the desired parse function:

parse :: [String] -> Either [String] Params
parse [x, y, z] =
  runValidation $ Params <$> parseStr x <*> parseDouble y <*> parseBool z
parse _ = Left ["wrong number of arguments."]

There are at least two reusable libraries that provide this functionality out of the box.


Old answer

You can't put Either String Int and Either String Char values into the same list, because they don't have the same type. As chi suggests, you can wrap the Right values in an existential type, or perhaps just eliminate them by mapping all values to Either String ().

If you're only looking for a way to collect all error messages, you can also use the lefts function.

Let's expand the example a bit:

import Data.Either

foo = Left "foo" :: Either String Int
bar = Left "bar" :: Either String Char
baz = Left "baz" :: Either String Char
qux = Right 42 :: Either String Int

list1 :: [Either String Int]
list1 = [foo, qux]

list2 :: [Either String Char]
list2 = [bar, baz]

You can put foo and qux in the same list, because they have the same type. The same goes for bar and baz.

You can pull out all the Left values of both lists and concatenate them:

errors :: [String]
errors = lefts list1 ++ lefts list2

Given the above values, errors has the value ["foo","bar","baz"]. The number 42 isn't included, because that was a Right value.

I get the impression, however, that this might be an XY problem. Which problem are you actually trying to solve?

Upvotes: 2

Related Questions