【问题标题】:function that can work with function with different type signatures可以与具有不同类型签名的函数一起使用的函数
【发布时间】:2016-01-03 06:57:21
【问题描述】:

我正在尝试在 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 的类型签名是什么?

【问题讨论】:

  • 这似乎是一个 XY 问题。你不会在 Haskell 中做这样的事情。您要解决的实际问题是什么?
  • 您不能编写将String -> IO()String -> String -> IO() 作为参数的函数。你需要Either (String -> IO()) (String -> String -> IO())。 (但过度依赖IO () 是一个强烈的信号,表明您正在尝试用不同的语言编写程序。)
  • hPutStrLnHandle 作为其第一个参数,而不是String。假设你打算使用putStrLn,那么else中的表达式类型为IO ()then中的表达式类型必须相同。因此,如果将hPutStrLn 更改为putStrLn(???) 的类型必须为IO (),因此functionBfunctionC 都不能通过。
  • @molbdnilo,严格来说并非如此。 f :: (String -> a) -> IO () 将接受String -> IO ()String -> String -> IO ()(以及第一个参数为String 类型的任何其他函数)。现在,您是否可以使用该类型制作有用的功能是另一回事!

标签: haskell


【解决方案1】:

一个函数有可能基于一个参数有“两个不同的签名”。这个函数实际上只有一个最通用的签名,但你可以伪造它,让它像那样工作。

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 () 的实例,它还有所有以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 替换您需要执行的任何操作。这几乎就是类型类的用途。 :-)

但是,我确实建议发布一个问题,详细说明您的问题,因为它闻起来可能有更清洁的解决方案。

【讨论】:

    【解决方案2】:

    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
    

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

    【讨论】:

      【解决方案3】:

      给定的代码不会按预期执行。如上所述,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 ()

      【讨论】:

        猜你喜欢
        • 2020-01-23
        • 2011-11-11
        • 2022-07-09
        • 2016-02-06
        • 2011-12-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多