Polymorphic input function with multiple argument types
Asked Answered
A

2

6

Out of sheer curiosity, I wonder if 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.

Algernon answered 25/2, 2021 at 14:55 Comment(0)
C
8

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
Cardenas answered 25/2, 2021 at 15:2 Comment(5)
Could you be so kind to work this out in an online REPL and share it here? It seems that I can't get your solution to compile either. I'll mark the answer as correct by then.Algernon
I don't know how to share code from an online repl -- perhaps you could tell me what kind of error you're getting? For what it's worth, if I go to repl.it and type in your code exactly, then substitute your foo type signature for mine and put {-# LANGUAGE RankNTypes #-} at the top, it works for me.Cardenas
Doing so yields this (compiler output in the comment below the code): paste.bka.li/view/0d66d0adAlgernon
First, make sure to notice that foo and bar are different functions with different types. Hopefully foo with the type I provide works for you, as it should. As for bar, it takes an argument function of type forall a. Show a => a -> String, and id does not have this type. Try calling bar with something like show or ((<> "!") . show).Cardenas
I've marked your answer as correct and supplied a written out example here: paste.bka.li/view/f64364fbAlgernon
R
4

* 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)
Rutherfurd answered 25/2, 2021 at 15:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.