我想编写“两次”函数,该函数需要一个函数和一个参数,并两次应用该函数。但是,它接收的功能应在联合类型上起作用。

例如。

    f a -> b
    f b -> c

输出量
   twice f a
 c
   f a
   b
   f b
   c
   f c
   error

例如。
f :: Int -> String
f :: String -> Char
twice f :: Int -> Cha

我该如何写有两种类型的f和做两次传递性事件的“两次”。

最佳答案

您实际上在这里问两件事:“如何编写twice函数?”和“如何编写两种不同类型的f?”。

让我们考虑第一个问题。现在让Haskell推断类型,让我们考虑一下它的外观。它需要一个参数:twice f = undefined。然后twice返回一个函数,该函数接受一个参数并将f应用于它两次:twice f = \x -> f (f x)

但是此函数的类型是什么?好吧,x必须为α类型。由于我们评估(f x),因此这意味着f必须是一个接受α并返回β的函数:f :: α -> β。但是,我们还要评估f (f x),因此f也必须也将β用作输入,并返回γ:f :: β -> γ。任何单个变量只能具有一种类型,因此这告诉我们α -> β = β -> γ,以及α = ββ = γ。因此,f :: α -> α,以及\x -> f (f x) :: α -> α;这意味着twice :: (α -> α) -> α -> α

这回答了您的第一个问题。您会注意到我在上面说过f必须是从一种类型到同一类型的函数。这回答了您的第二个问题:不可能编写两种不同类型的f。正如我所说,这是因为任何单个变量只能具有一种(可能是多态的)类型。为什么?好吧,除其他原因外,假设我们有一个带有两个类型签名的impossibleimpossible :: Int以及两个绑定impossible :: Stringimpossible = 24变量impossible = "absz"。那么show impossible返回什么? show函数的类型为show :: Show α => α -> String;由于IntString都是Show类型类的实例,因此我们无法确定这将返回"42"还是"\"absz\""。这样的不一致是为什么我们只允许一种类型的原因。

但是,所有希望都不会丢失!您还提到了使用联合类型来实现f。在这种情况下,您可能指的是Either类型(尽管Haskell中的所有数据类型都是联合类型的一种形式,称为区分联合)。 Either是带有两个类型参数的类型(就像[]一样,列表类型也需要一个)。我们说它具有kind [类型的类型] Either :: * -> * -> *)。 Either是联合类型:Either A BA的所有元素和B的所有元素组成,并提升为Either。就像迈克尔·斯蒂尔(Michael Steele)所说的那样,您可以编写带有两个类型签名的函数作为返回Either值的函数:f :: Either δ ε -> Either δ ε。请注意,这是作为参数传递给twice的完全有效的值,因为Either δ ε是完全合法的类型。我们通过模式匹配在Either上定义函数; Either的两个构造函数是Left :: δ -> Either δ εRight :: ε -> Either δ ε,用于提升两种类型的值。样例函数看起来像

f :: Either Int String -> Either Int String
f (Left  n) = Right $ "The number " ++ show n
f (Right s) = Left  $ length s

-- f (Left 3)               == Right "The number 3"
-- f (Right "The number 3") == Left 12
-- twice f (Left 3)         == Left 12

如果您真的想模仿您的示例并经历三种类型,从αβγ,则可以使用嵌套的Either或定义自己的数据类型。使用嵌套的Either,您将获得
f :: Either Int (Either String Char) -> Either Int (Either String Char)
f (Left  n)         = Right $ Left  $ "The number " ++ show n
f (Right (Left  s)) = Right $ Right $ head $ drop 11 s
f (Right (Right c)) = Left  $ fromEnum c

-- f (Left 42)                      == Right (Left "The number 42")
-- f (Right (Left "The number 42")) == Right (Right '4')
-- f (Right (Right '4'))            == Left 52
-- twice f (Left 42)                == Right (Right '4')

使用新类型,您将获得:
data Either3 a b c = Left3 a | Mid3 b | Right3 c deriving (Eq, Ord, Read, Show)

f :: Either3 Int String Char -> Either3 Int String Char
f (Left3  n) = Mid3   $ "The number " ++ show n
f (Mid3   s) = Right3 $ head $ drop 11 s
f (Right3 c) = Left3  $ fromEnum c

-- f (Left3 42)             == Mid3 "The number 42"
-- f (Mid3 "The number 42") == Right3 '4'
-- f (Right3 '4')           == Left3 52
-- twice f (Left3 42)       == Right3 '4'

您还可以定义一个特定的data MyType = MyInt Int | MyStr String | MyChar Char,然后将每个Either3 Int String Char替换为MyType,将每个Left3替换为MyInt,将每个Mid3替换为MyStr,将每个Right3替换为MyChar;这实际上是同一件事,但不太普遍。

请注意,由于Haskell的行为,我们可以将原始twice重写为twice f x = f (f x)。实际上,更简单地说,如果导入twice f = f (.) f,则可以将其编写为twice = join (.)Control.Monad。这与回答这个问题的目的无关,但是出于其他原因(尤其是(->) αMonad实例,我不太了解)很有趣。如果您以前没看过,可能要看一下。

关于haskell - 如何在Haskell中编写函数“两次”?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/2484270/

10-11 22:35
查看更多