Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I use the type `Show a => [Something -> a]`?

Tags:

haskell

I have a record type say

data Rec {
    recNumber :: Int
  , recName :: String
  -- more fields of various types
}

And I want to write a toString function for Rec :

recToString :: Rec -> String
recToString r = intercalate "\t" $ map ($ r) fields
  where fields = [show . recNumber, show . recName]

This works. fields has type [Rec -> String]. But I'm lazy and I would prefer writing

recToString r = intercalate "\t" $ map (\f -> show $ f r) fields
  where fields = [recNumber, recName]

But this doesn't work. Intuitively I would say fields has type Show a => [Rec -> a] and this should be ok. But Haskell doesn't allow it.

I'd like to understand what is going on here. Would I be right if I said that in the first case I get a list of functions such that the 2 instances of show are actually not the same function, but Haskell is able to determine which is which at compile time (which is why it's ok).

[show . recNumber, show . recName]
   ^-- This is show in instance Show Number
                     ^-- This is show in instance Show String

Whereas in the second case, I only have one literal use of show in the code, and that would have to refer to multiple instances, not determined at compile time ?

map (\f -> show $ f r) fields
            ^-- Must be both instances at the same time 

Can someone help me understand this ? And also are there workarounds or type system expansions that allow this ?

like image 725
ARRG Avatar asked Feb 23 '15 17:02

ARRG


1 Answers

The type signature doesn't say what you think it says.

This seems to be a common misunderstanding. Consider the function

foo :: Show a => Rec -> a

People frequently seem to think this means that "foo can return any type that it wants to, so long as that type supports Show". It doesn't.

What it actually means is that foo must be able to return any possible type, because the caller gets to choose what the return type should be.

A few moments' thought will reveal that foo actually cannot exist. There is no way to turn a Rec into any possible type that can ever exist. It can't be done.

People often try to do something like Show a => [a] to mean "a list of mixed types but they all have Show". That obviously doesn't work; this type actually means that the list elements can be any type, but they still have to be all the same.

What you're trying to do seems reasonable enough. Unfortunately, I think your first example is about as close as you can get. You could try using tuples and lenses to get around this. You could try using Template Haskell instead. But unless you've got a hell of a lot of fields, it's probably not even worth the effort.

like image 156
MathematicalOrchid Avatar answered Oct 14 '22 09:10

MathematicalOrchid