1
votes

I'm trying to write a function in haskell that takes two parameters and a function. Depending on the two parameters it will perform the given function or not. The problem is that the given functions can have a different type signatures. In pseudo code:

functionB:: String -> IO()


functionC:: String -> String -> IO() 

functionA :: String -> String ->(???)-> IO()
functionA parm1 parm2 f = if parm1 == parm2
                          then f
                          else hPutStrLn "error"

FunctionA should be able to work with function C or B and as you can see B and C have a different type signature. Can you do this in Haskell? If so what will be the type signature of functionA?

3
That seems like a XY problem. You wouldn't do something like this in Haskell. What's your actual problem you're trying to solve?Zeta
You can't write a function that takes either a String -> IO() or a String -> String -> IO() as parameter. You'd need Either (String -> IO()) (String -> String -> IO()). (But over-reliance of IO () is a strong sign that you're trying to write a program in a different language.)molbdnilo
hPutStrLn takes a Handle as its first argument, not a String. Assuming you meant to use putStrLn, the type of the expression in the else is IO (), and the type of the expression in the then must have the same type. Therefore, if you change hPutStrLn to putStrLn, the (???) must have type IO (), so neither functionB nor functionC can be passed.pat
@molbdnilo, that's not strictly true. f :: (String -> a) -> IO () would accept either String -> IO () or String -> String -> IO () (as well as any other function whose first argument was of type String). Now, whether you could make a useful function with that type is another matter!pat

3 Answers

3
votes

It is possible for a function to have "two different signatures" based on an argument. The function will really only have one most general signature, but you can fake it so it works like that.

functionA :: String -> String -> a -> a
functionA parm1 parm2 f = if parm1 == parm2 
                          then f
                          else putStrLn "error" -- uh oh

The signature says that this will return the same kind of thing as its third argument. So:

functionA "foo" "bar" functionB :: String -> IO ()
functionA "foo" "bar" functionC :: String -> String -> IO ()

In other words, the first line takes one additional argument (four total), and the second line takes two (five total).

The trouble is that it doesn't work, because putStrLn "error" doesn't have either of those types, and we need it to have "both".

One way through this is to make a typeclass which characterizes the operations you need on both of those types. In this case, maybe printing an error is the thing you want?

{-# LANGUAGE FlexibleInstances #-}

class CanPrintError a where
    printError :: String -> a

instance CanPrintError (IO ()) where
    -- specialized to:
    -- printError :: String -> IO ()
    printError = putStrLn

instance (CanPrintError b) => CanPrintError (a -> b) where
    -- specialized to:
    -- printError :: (CanPrintError b) => String -> a -> b
    printError err _ = printError err

Notice that I have made the second instance recursive, so CanPrintError doesn't only have instances for String -> IO () and String -> String -> IO (), it has instances for all multiple-arg functions ending in IO () such as

String -> Int -> (Int -> Maybe Bool) -> IO ()

It is possible to just make it for the two specific types in question, though it makes me question your motivations.

Now we just add the necessary constraint to functionA's signature:

functionA :: (CanPrintError a) => String -> String -> a -> a
functionA parm1 parm2 f = if parm1 == parm2
                          then f
                          else printError "error"

You can imagine substituting for printError whatever operation you need to do. This is pretty much what typeclasses are for. :-)

However, I do suggest posting a question with more specific details of your problem, since it smells like something that might have a cleaner solution.

3
votes

The function const creates a new function that always returns the first parameter regardless of what you pass to it. It's type signature is:

const :: a -> b -> a

This means we can create things like:

> const 4 "String"
4

> let fn = const (\x -> print x)
> :t fn
fn :: Show a => b -> a -> IO ()
> fn [1, 2, 3, 4, 5] (Just 3)
Just 3

In your example, you can use this function to pass a "const'd" version of functionB to functionA:

functionB x = putStrLn x
functionC x y = putStrLn x >> putStrLn y 

functionA :: String -> String -> (String -> String -> IO()) -> IO()
functionA parm1 parm2 f = if parm1 == parm2
                          then f parm1 parm2
                          else putStrLn "error"

> functionA "Foo" "Foo" (const functionB)
Foo
> functionA "Foo" "Foo" functionC
Foo
Foo
> functionA "Foo" "Bar" undefined
error
0
votes

The given code wouldn't perform as expected. As mentioned above, hPutStrln requires a file handle, did you intend putStrLn? If so, the signature of putStrLn is String -> IO () and so putStrLn "error" is just IO (). Since the line else f provides no argument for f, the type signature of f must be IO () - the type signature would be functionA :: String -> String -> IO () -> IO () (actually, it could be functionA :: Eq t => t -> t -> IO () -> IO () since the there is nothing requiring a string yet).

The problem is if you can't make it so that f can take in one or two inputs, it only only be or the other. You could replace else f with else f parm1 and then f would be of type String -> IO () or with else parm1 parm2 and then f would be of type String -> String -> IO ().