简体   繁体   中英

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

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 ?

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.

The type you actually want is not:

Show a => [Rec -> a]

Any type declaration with unbound type variables has an implicit forall . The above is equivalent to:

forall a. Show a => [Rec -> a]

This isn't what you wan't, because the a must be specialized to a single type for the entire list. (By the caller, to any one type they choose, as MathematicalOrchid points out.) Because you want the a of each element in the list to be able to be instantiated differently... what you are actually seeking is an existential type.

[exists a. Show a => Rec -> a]

You are wishing for a form of subtyping that Haskell does not support very well. The above syntax is not supported at all by GHC. You can use newtypes to sort of accomplish this:

{-# LANGUAGE ExistentialQuantification #-}
newtype Showy = forall a. Show a => Showy a

fields :: [Rec -> Showy]
fields = [Showy . recNumber, Showy . recName]

But unfortunatley, that is just as tedious as converting directly to strings, isn't it?


I don't believe that lens is capable of getting around this particular weakness of the Haskell type system:

recToString :: Rec -> String
recToString r = intercalate "\t" $ toListOf (each . to fieldShown) fields
  where fields = (recNumber, recName)
        fieldShown f = show (f r)

-- error: Couldn't match type Int with [Char]

Suppose the fields do have the same type:

fields = [recNumber, recNumber]

Then it works, and Haskell figures out which show function instance to use at compile time; it doesn't have to look it up dynamically.

If you manually write out show each time, as in your original example, then Haskell can determine the correct instance for each call to show at compile time.

As for existentials... it depends on implementation, but presumably, the compiler cannot determine which instance to use statically, so a dynamic lookup will be used instead.

I'd like to suggest something very simple instead:

recToString r = intercalate "\t" [s recNumber, s recName]
  where s f = show (f r)

All the elements of a list in Haskell must have the same type, so a list containing one Int and one String simply cannot exist. It is possible to get around this in GHC using existential types, but you probably shouldn't (this use of existentials is widely considered an anti-pattern, and it doesn't tend to perform terribly well). Another option would be to switch from a list to a tuple, and use some weird stuff from the lens package to map over both parts. It might even work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM