Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transforming functions of type `a -> b` into those of type `String -> String` in Haskell

My intention is simple. I want to wrap functions of type a -> b into String -> String (so that a bunch of heterogeneous functions can be put in a list). So I write:

wrap :: (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s :: a)

However, ghc complaints:

Could not deduce (Read a1) arising from a use of `read'
from the context (Read a, Show b)
  bound by the type signature for
             wrap :: (Read a, Show b) => (a -> b) -> String -> String

I want to know why my piece of code won't work and what kind of hacks is needed to achieve my goal?

Thanks.

like image 299
tfboy Avatar asked Aug 07 '13 14:08

tfboy


2 Answers

Your code won't work because Haskell doesn't re-use or scope type variables; the a in wrap :: (Read a, Show b) => (a -> b) -> (String -> String) is a completely different a from the one in read s :: a (and they're both universally quantified). This is the source of the a1 in the error message; GHC is alpha-converting the program to

wrap :: (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s :: a1)

However, f's argument type is fixed inside wrap, so simply removing the type annotation works fine. Your function becomes

wrap :: (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s)
  -- Or wrap f = show . f . read

And you can use it:

ghci> map ($ "42") [wrap (+ (7 :: Integer)), wrap (* (2.0 :: Double))]
["49","84.0"]

Note that this means that read s has a type you can't write down. In Haskell 2010 (or 98), the only way to get around this is to use a function like asTypeOf :: a -> a -> a; asTypeOf is just const, but thanks to its type signature, it constrains its first, returned, argument to be of the same type as its second. Then, of course, you'd have to conjure up a variable of type a. The following would work for that:

wrap :: (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s `asTypeOf` fInput)
  where fInput = undefined
        fOutput = f fInput -- I still can't give this a type signature

In GHC, to avoid this, you can turn on the ScopedTypeVariables extension; with that on, if you explicitly qualify all your type variables with a forall, they'll be scoped just like value-level names. Then your code would become

{-# LANGUAGE ScopedTypeVariables #-}
wrap :: forall a b. (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s :: a)

But remember, you don't need any type annotations at all for this simple example.

like image 191
Antal Spector-Zabusky Avatar answered Sep 30 '22 22:09

Antal Spector-Zabusky


To specify the type of read s explicitly, you'll need something like ScopedTypeVariables:

{-# LANGUAGE ScopedTypeVariables #-}
...
wrap :: forall a b. (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s :: a)

Since otherwise the :: a annotation inside the function refers to a different type from the a in the type signature (it implicitly means :: forall a. a). But note that you can also just drop the type annotation entirely:

wrap :: (Read a, Show b) => (a -> b) -> (String -> String)
wrap f = \s -> show $ f (read s)

Since the type of read s can be inferred. That also lets you simplify the body to

wrap f = show . f . read
like image 29
shachaf Avatar answered Sep 30 '22 22:09

shachaf