Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic input function with multiple argument types

Out of sheer curiosity, I wonder of something like the following is possible in Haskell: A function foo takes another function as an argument that is called in the body of foo more than once, changing the type of the arguments along the way.

This following code does not compile because fn's argument's types are pinned down once it is called, but hopefully it illustrates what I'm babbling about.

main = putStrLn (foo id)

foo :: (* -> *) -> [Char] -- maybe I'm also getting the whole *-thing wrong
foo fn =
    let
        val1 = fn "hey"
        val2 = fn 42
    in
        show (val1, val2)

I wonder if it can be achieved at all, and if you can do it without helpers like typeclasses.

like image 698
Aarkon Avatar asked Feb 25 '21 14:02

Aarkon


2 Answers

What you're looking for is an extension called RankNTypes. With it, you can write the type of your function as:

{-# LANGUAGE RankNTypes #-}

foo :: (forall a. a -> a) -> [Char]

In this case, the only function you can possibly supply is id, but you can also use type classes to allow slightly more interesting polymorphic functions as arguments. Consider this version of your function:

bar:: (forall a. Show a => a -> String) -> String
bar fn =
    let
        val1 = fn "hey"
        val2 = fn 42
    in
        val1 <> val2
like image 136
DDub Avatar answered Nov 09 '22 23:11

DDub


* is not a wildcard to allow any type to be used. Therefore, you use of that is wrong.

To type your function, we need to specify the type of fn. That must be a polymorphic function returning some value that can be Showed.

A possible solution is:

{-# LANGUAGE ScopedTypeVariables, RankNTypes #-}

foo :: forall b. Show b => (forall a. a -> b) -> [Char]
foo fn = let
   val1 = fn "hey"
   val2 = fn 42
   in show (val1, val2)

This requires fn to accept any type a and return a fixed type b which is of class Show.

As written, this is not terribly useful since there is no way that fn can make use of its argument, since it is of a generic type a.

Perhaps a more useful variant could be one where a belongs to some typeclass c, so that at least the argument of fn can be used according to c.

foo :: forall b c. (Show b, c String, c Int) 
    => (forall a. c a => a -> b) -> [Char]
foo fn = let
   val1 = fn "hey"
   val2 = fn (42 :: Int)
   in show (val1, val2)
like image 44
chi Avatar answered Nov 09 '22 23:11

chi