可以使用具有不同类型签名的函数的函数

时间:2016-01-01 18:05:41

标签: haskell

我正在尝试在haskell中编写一个带有两个参数和一个函数的函数。根据这两个参数,它将执行给定的功能。问题是给定的函数可以具有不同的类型签名。在伪代码中:

functionB:: String -> IO()


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

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

FunctionA应该能够使用函数C或B,正如您所看到的,B和C具有不同的类型签名。你能在Haskell做到这一点吗?如果是这样,functionA的类型签名是什么?

3 个答案:

答案 0 :(得分:3)

一个函数可能有两个不同的签名"基于一个论点。该函数实际上只有一个最常见的签名,但你可以伪造它,所以它就像那样。

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

签名表示这将返回与第三个参数相同的东西。所以:

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

换句话说,第一行需要一个额外的参数(总共四个),第二行需要两个(总共五个)。

问题在于它不起作用,因为putStrLn "error"没有这些类型中的任何一种类型,我们需要它具有"两者"。

通过这种方法的一种方法是创建一个类型类,它表征了这两种类型所需的操作。在这种情况下,打印错误是你想要的吗?

{-# 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

请注意,我已将第二个实例递归,因此CanPrintError不仅包含String -> IO ()String -> String -> IO ()的实例,它具有所有多个arg函数结束的实例在IO ()中,例如

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

可以针对这两种特定类型制作它,但它会让我质疑你的动机。

现在我们只需要为functionA的签名添加必要的约束:

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

您可以想象用printError替换您需要做的任何操作。这几乎是类型类的用途。 : - )

但是,我建议您发布一个问题,其中包含您问题的更多具体细节,因为它的味道可能更清晰。

答案 1 :(得分:3)

函数const创建一个新函数,无论传递给它的是什么,它总是返回第一个参数。它的类型签名是:

const :: a -> b -> a

这意味着我们可以创建以下内容:

> 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

在您的示例中,您可以使用此功能将“const”版本的functionB传递给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

答案 2 :(得分:0)

给定的代码不会按预期执行。如上所述,hPutStrln需要文件句柄,您打算putStrLn吗?如果是,则putStrLn的签名为String -> IO (),因此putStrLn "error"仅为IO ()。由于行else f没有为f提供参数,因此f的类型签名必须为IO () - 类型签名为functionA :: String -> String -> IO () -> IO ()(实际上,它可能是functionA :: Eq t => t -> t -> IO () -> IO (),因为没有什么需要字符串了)。

问题是,如果你不能做到这样,f可以接受一个或两个输入,它只能是或另一个。您可以将else f替换为else f parm1,然后f将为String -> IO ()类型或else parm1 parm2类型,然后f将为String -> String -> IO ()类型。