我想编写“两次”函数,该函数需要一个函数和一个参数,并两次应用该函数。但是,它接收的功能应在联合类型上起作用。
例如。
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
。正如我所说,这是因为任何单个变量只能具有一种(可能是多态的)类型。为什么?好吧,除其他原因外,假设我们有一个带有两个类型签名的impossible
和impossible :: Int
以及两个绑定impossible :: String
和impossible = 24
变量impossible = "absz"
。那么show impossible
返回什么? show
函数的类型为show :: Show α => α -> String
;由于Int
和String
都是Show
类型类的实例,因此我们无法确定这将返回"42"
还是"\"absz\""
。这样的不一致是为什么我们只允许一种类型的原因。
但是,所有希望都不会丢失!您还提到了使用联合类型来实现f
。在这种情况下,您可能指的是Either
类型(尽管Haskell中的所有数据类型都是联合类型的一种形式,称为区分联合)。 Either
是带有两个类型参数的类型(就像[]
一样,列表类型也需要一个)。我们说它具有kind [类型的类型] Either :: * -> * -> *
)。 Either
是联合类型:Either A B
由A
的所有元素和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/