我想写'两次'函数,它接受一个函数和一个参数并将该函数应用两次。但是它收到的函数应该适用于联合类型。
例如
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和执行传递性事物的“两次”。
答案 0 :(得分:15)
你真的在这里问两件事:“我如何编写函数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
是一个带有两个类型参数的类型(就像[]
一样,列表类型需要一个);我们说它有种类 [类型的类型] 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
twice
;这实际上是相同的,但不太通用。
请注意,由于Haskell的currying,我们可以将原始twice f x = f (f x)
重写为twice f = f (.) f
。事实上,更简单的是,如果我们导入twice = join (.)
,我们可以将其写为Control.Monad
或(->) α
。这与回答此问题的目的无关,但由于其他原因(尤其是Monad
的{{1}}实例而感兴趣,我并不完全理解);如果你以前没有看过它,你可能想看看。
答案 1 :(得分:5)
您的传递性内容通常称为功能组合,可通过.
运营商获取。
f . g = \x -> f(g x)
Twice
是一个自我组合的示例(函数迭代),您可以通过f . f
来表达。
但请注意,Haskell中没有重载函数 - 一个作用域中的每个函数都只有一种类型和实现(尽管这种类型可能是多态的)。
答案 2 :(得分:4)
这样的twice
函数看起来像这样:
twice :: (a -> a) -> a -> a
twice f = f . f
假设您有一个名为sayIt
的函数,可将Int
值转换为英语。
sayIt :: Int -> String
sayIt 1 = "One"
sayIt _ = "I don't know!"
无法使twice
功能与sayIt
配合使用:
*Main> sayIt (sayIt 1)
<interactive>:1:7:
Couldn't match expected type `Int' against inferred type `String'
In the first argument of `sayIt', namely `(sayIt 1)'
In the expression: sayIt (sayIt 1)
In the definition of `it': it = sayIt (sayIt 1)
*Main> twice sayIt 1
<interactive>:1:6:
Couldn't match expected type `Int' against inferred type `String'
In the first argument of `twice', namely `sayIt'
In the expression: twice sayIt 1
In the definition of `it': it = twice sayIt 1
sayIt
只需Int
个值,因此第二次调用String
值时会出错。
您只能将twice
用于获取和返回相同类型的函数。既然你问过“联合”类型,这里有一个这样一个函数的例子:
sayIt2 :: Either Int String -> Either Int String
sayIt2 (Left 1) = Right "One"
sayIt2 (Right str) = Right str
sayIt2 _ = Right "I don't know!"
示例:
*Main> twice sayIt2 (Left 1)
Right "One"
*Main> twice sayIt2 (Left 2)
Right "I don't know!"
*Main> twice sayIt2 (Right "Hello")
Right "Hello"
答案 3 :(得分:3)
twice f = f . f