我想学习Haskell和我经历了所有的基础知识很。 但现在我卡住了,试图让我的头周围的仿函数。
我读过“仿函数转换一个类别到另一个类别”。 这是什么意思?
我知道这是一个好多问,但任何人都可以给我仿函数的一个普通的英语解释,或者一个简单的使用情况 ?
我想学习Haskell和我经历了所有的基础知识很。 但现在我卡住了,试图让我的头周围的仿函数。
我读过“仿函数转换一个类别到另一个类别”。 这是什么意思?
我知道这是一个好多问,但任何人都可以给我仿函数的一个普通的英语解释,或者一个简单的使用情况 ?
模糊的解释是一个Functor
是某种容器和相关功能的fmap
,可以让你改变任何包含,考虑到转换所包含的功能。
例如,列表是这种容器的,使得fmap (+1) [1,2,3,4]
产率[2,3,4,5]
Maybe
还可以做一个仿函数,这样fmap toUpper (Just 'a')
产生Just 'A'
。
一般类型的fmap
节目相当整齐地是怎么回事:
fmap :: Functor f => (a -> b) -> f a -> f b
和专业版本可能会使其更清晰。 下面是列表版本:
fmap :: (a -> b) -> [a] -> [b]
而也许版本:
fmap :: (a -> b) -> Maybe a -> Maybe b
你可以得到在标准信息Functor
与查询GHCI实例:i Functor
和许多模块定义的多个实例Functor
(以及其他类型的类)■
请不要走字“容器”太当回事,虽然。 Functor
s为一个明确的概念,但你可以经常的原因吧这个模糊的比喻。
在了解正在发生的事情简直是读取每个实例,这应该给你是怎么回事直觉的定义您最好的选择。 从那里,这只是一小步,真正正式的概念的理解。 有什么需要添加一个清楚地了解我们的“容器”还真是,而且每个实例多满足一对简单的定律。
我不小心写了
我将使用实例回答你的问题,我就把这类型下面的评论。
当心的类型模式。
fmap
是的一般化map
函子是给你的fmap
功能。 fmap
作品像map
,让我们看看map
先:
map (subtract 1) [2,4,8,16] = [1,3,7,15]
-- Int->Int [Int] [Int]
因此,它使用的功能(subtract 1)
名单内 。 事实上,对于名单, fmap
做exaclty什么map
呢。 让我们从10这时候乘的一切:
fmap (* 10) [2,4,8,16] = [20,40,80,160]
-- Int->Int [Int] [Int]
我会形容这是映射由10在列表上相乘的功能。
fmap
也适用于Maybe
还有什么可以我fmap
过? 让我们用Maybe数据类型,它有两种类型的价值观, Nothing
和Just x
。 (您可以使用Nothing
代表未能得到答案,而Just x
代表一个答案。)
fmap (+7) (Just 10) = Just 17
fmap (+7) Nothing = Nothing
-- Int->Int Maybe Int Maybe Int
OK,如此反复, fmap
使用(+7)
的也许里面 。 我们可以FMAP等功能了。 length
找到一个列表的长度,所以我们可以FMAP过来Maybe [Double]
fmap length Nothing = Nothing
fmap length (Just [5.0, 4.0, 3.0, 2.0, 1.573458]) = Just 5
-- [Double]->Int Maybe [Double] Maybe Int
其实length :: [a] -> Int
,但我在这里用它在[Double]
所以我专门它。
让我们用show
把东西成字符串。 偷偷的实际类型show
是Show a => a -> String
,但是这是一个有点长,我在这里使用它的Int
,所以专门来Int -> String
。
fmap show (Just 12) = Just "12"
fmap show Nothing = Nothing
-- Int->String Maybe Int Maybe String
同时,回头名单
fmap show [3,4,5] = ["3", "4", "5"]
-- Int->String [Int] [String]
fmap
的作品Either something
让我们用它在一个稍微不同的结构, Either
。 类型的值Either ab
要么Left a
值或Right b
值。 有时我们用要么表示一个成功Right goodvalue
或失败Left errordetails
,有时候只是两种类型的值混合连成一片。 总之,对于无论是数据类型的仿函数只在作品上Right
-它留下Left
单独值。 这尤其是如果你使用正确的价值观作为成功的(事实上,我们不就能够使它在两个工作,因为类型不一定相同)是有道理的。 让使用类型Either String Int
作为一个例子
fmap (5*) (Left "hi") = Left "hi"
fmap (5*) (Right 4) = Right 20
-- Int->Int Either String Int Either String Int
它使(5*)
的任一内工作,但对于Eithers,只有Right
价值观得到改变。 但是,我们可以做的另一种方式圆Either Int String
,只要功能适用于字符串。 让我们把", cool!"
在东西年底,使用(++ ", cool!")
fmap (++ ", cool!") (Left 4) = Left 4
fmap (++ ", cool!") (Right "fmap edits values") = Right "fmap edits values, cool!"
-- String->String Either Int String Either Int String
fmap
对IO 现在,我最喜欢使用的FMAP方法之一是使用它的IO
值编辑一些IO操作带给我的价值。 让我们,让你键入的东西,然后打印出来马上一个例子:
echo1 :: IO ()
echo1 = do
putStrLn "Say something!"
whattheysaid <- getLine -- getLine :: IO String
putStrLn whattheysaid -- putStrLn :: String -> IO ()
我们可以写在整洁的感觉对我道:
echo2 :: IO ()
echo2 = putStrLn "Say something"
>> getLine >>= putStrLn
>>
做了一个又一个的事情,但我喜欢这样做的原因是因为>>=
采取的字符串getLine
给我们,并送入putStrLn
这需要一个字符串。 如果我们想只是问候用户:
greet1 :: IO ()
greet1 = do
putStrLn "What's your name?"
name <- getLine
putStrLn ("Hello, " ++ name)
如果我们想写的是,在简洁的方式我有点卡住了。 我不得不写
greet2 :: IO ()
greet2 = putStrLn "What's your name?"
>> getLine >>= (\name -> putStrLn ("Hello, " ++ name))
这是不是比更好do
版本。 事实上, do
记号是有那么你没有做到这一点。 但可以fmap
出手相救? 是的,它可以。 ("Hello, "++)
是,我可以在FMAP的函数getline的功能!
fmap ("Hello, " ++) getLine = -- read a line, return "Hello, " in front of it
-- String->String IO String IO String
我们可以用这样的:
greet3 :: IO ()
greet3 = putStrLn "What's your name?"
>> fmap ("Hello, "++) getLine >>= putStrLn
我们可以拉动我们付出任何代价这一招。 让我们以是否“真”或“假”被键入不同意:
fmap not readLn = -- read a line that has a Bool on it, change it
-- Bool->Bool IO Bool IO Bool
还是让我们只报告文件的大小:
fmap length (readFile "test.txt") = -- read the file, return its length
-- String->Int IO String IO Int
-- [a]->Int IO [Char] IO Int (more precisely)
fmap
做,它有什么它做什么? 如果你一直在看的类型模式和思考的例子,你会注意到,FMAP需要,在某些价值观工作的功能,并应用功能上的东西,有或以某种方式产生这些值,编辑值。 (例如readLn是读布尔,所以不得不键入IO Bool
在于它产生一个读出在它有一个布尔值Bool
,EG2 [4,5,6]
具有Int
S IN它)。
fmap :: (a -> b) -> Something a -> Something b
这个工程的东西是列表的的(书面[]
Maybe
, Either String
, Either Int
, IO
和过负荷的事情。 我们把它叫做一个仿函数,如果这个工程在合理的方式(也有一些规则 - 后)。 FMAP的实际类型
fmap :: Functor something => (a -> b) -> something a -> something b
但我们通常替代something
与f
赘述。 这都是相同的编译器,但是:
fmap :: Functor f => (a -> b) -> f a -> f b
回头看看在类型和检查这个总是工作-件事Either String Int
仔细-什么是f
那个时候?
id
是身份的功能:
id :: a -> a
id x = x
下面是规则:
fmap id == id -- identity identity
fmap (f . g) == fmap f . fmap g -- composition
首先身份认同:如果映射,什么也不做的功能,也不会改变任何东西。 这听起来很明显(很多的规则做),但你可以解释说的话说, fmap
只允许更改值,而不是结构。 fmap
不允许把Just 4
成Nothing
,或者[6]
为[1,2,3,6]
或Right 4
成Left 4
因为不止是数据变化-结构或上下文数据改变。
我打这个规则一次,当我工作的一个图形用户界面项目 - 我希望能够编辑值,但是底下不改变结构我不能这样做。 没有人会拥有真正注意到的差异,因为它有同样的效果,但意识到这一点并没有遵守规则的函子让我重新思考我的整个设计,以及更快现在是更清洁,雨衣和。
其次组成:这意味着你可以选择是否在同一时间FMAP一个功能,或者在同一时间FMAP他们。 如果fmap
离开只有你的价值观的结构/背景,只是它们进行编辑,其提供的功能,它会与这一规则工作了。
为什么我们呢? 为了确保fmap
不悄悄做幕后任何事或任何改变,我们没有想到的。 他们不是由编译器( - 程序员应该检查要求编译器来证明一个定理它编译之前,你的代码是不公平的,而且会慢下来编译)执行。 这意味着你可以欺骗,但是这是一个不错的计划因为你的代码可以给出意想不到的结果。
它保持独立于你的头函子,并在其中应用了仿函数类型的值之间的区别是很重要的。 一个仿函数本身是一个类型构造像Maybe
, IO
,或者该列表构造[]
在函子的值与施加该类型的构造类型一些特定的值。 例如Just 3
是在类型一个特定值Maybe Int
(即类型是Maybe
的仿函数应用到该类型Int
), putStrLn "Hello World"
是在类型一个特定值IO ()
和[2, 4, 8, 16, 32]
是在类型一个特定值[Int]
我喜欢与应用为“相同”作为基本类型的值,但也有一些额外的“上下文”仿函数类型考虑的值。 人们经常使用的容器比喻为一个仿函数,它很自然地适用于相当多的函子,但随后变得更加的障碍比当你不得不说服自己,帮助的IO
或(->) r
就像是一个容器。
因此,如果一个Int
为整数,那么Maybe Int
表示可以不存在的整数值(“可以不存在”是“上下文”)。 一个[Int]
表示与多个可能的值(这是列表函子的相同的解释列表单子的“确定性”的解释)的整数值。 一个IO Int
表示整数值,其精确值取决于整个宇宙(或备选地,它代表了可以通过运行一个外部进程来获得的整数值)。 甲Char -> Int
为任何整数值Char
值(“函数以r
作为参数”是用于任何类型的函子r
;具有r
为Char
(->) Char
是类型构造这是一个算符,它施加到Int
变为(->) Char Int
或Char -> Int
中缀表示法)。
你可以用一般的函子做的唯一事情是fmap
,用型Functor f => (a -> b) -> (fa -> fb)
fmap
变换,关于正常值成上与由算符加入另外的上下文值进行操作的功能动作的功能; 究竟这样做是对每个函子不同,但你可以与他们做到这一点。
所以与Maybe
算符fmap (+1)
是计算一个可能不在场整数1比其输入可能不在场整数更高的功能。 与列表算符fmap (+1)
是计算的非确定性整数1比其输入非确定性整数更高的功能。 与IO
算符, fmap (+1)
是计算的整数1比其输入更高的功能整数,其值-取决于-上的外部的宇宙。 随着(->) Char
函子, fmap (+1)
是指添加1〜它依赖于一个整数的功能Char
(当我养活一个Char
的返回值,我得到的比我会通过已经得到较高的1供给相同的Char
到原始值)。
但在一般情况下,对于一些未知的函子f
, fmap (+1)
适用于一些价值f Int
是“仿版”的功能(+1)
普通Int
秒。 它增加了1到什么样的“背景”这个特别的函子整数。
就其本身而言, fmap
不一定是有用的。 通常,当你写一个具体的方案,并用函子的工作,你有一个特别的函子的工作,你常常想起fmap
因为无论它对于特定函子 。 当我工作[Int]
我经常没有想到我的[Int]
值不确定性的整数,我只是认为他们作为整数列表,我想的fmap
以同样的方式,我认为的map
。
那么,为什么用函子烦? 为什么不只是map
的列表, applyToMaybe
的Maybe
秒, applyToIO
为IO
S' 然后大家就知道他们做什么,没有人会明白像仿函数怪异抽象的概念。
关键是,有很多仿函数在那里的识别; 几乎所有的容器类型开始(因此容器比喻什么函子)。 他们每个人都有相应的操作fmap
,即使我们没有仿函数。 当你写一个算法仅在方面fmap
操作(或map
,或任何它被称为为特定类型),然后如果你把它写在仿函数,而不是你的特定类型的条款,然后它适用于所有的仿函数。
它也可以作为文件的形式。 如果我的手从我的列表值之一,你写一个函数,在列表操作,它可以做任何事情都。 但是,如果我的手从我的名单给你写一个函数,在任意仿函数的值进行操作,那么我就知道你的函数的实现可以不使用列表功能,只仿函数特征。
你将如何使用functorish事情在传统的命令式编程可能有助于见效益遥想。 有容器类型如数组,列表,树等,通常必须使用遍历他们一些模式。 它可以为不同的容器略有不同,虽然库通常提供标准的接口迭代来解决这个问题。 但你还是最终要在它们之间迭代,当你想要做的是计算结果在容器中的每个项目,并收集所有的结果,你通常最终会在逻辑混合每次写一点for循环为你去构建新的容器。
fmap
是每一个为形式,你永远不会写,整理一劳永逸由库的作家,之前,你甚至坐下来计划的循环。 另外,它也可以与喜欢的东西使用Maybe
和(->) r
,可能不会被视为具有任何与一个设计在命令式语言一致的容器接口。
在Haskell,仿函数拍摄具有的“东西”容器的概念,这样你可以操作该“东西”,而不改变容器的形状。
函子提供一个功能, fmap
,可以让你做到这一点,采取常规的功能和“解除”它从一个类型的元素的容器到另一个功能:
fmap :: Functor f => (a -> b) -> (f a -> f b)
例如, []
列表类型构造,是一个算符:
> fmap show [1, 2, 3]
["1","2","3"]
等许多其他Haskell的类型构造,像Maybe
和Map Integer
1:
> fmap (+1) (Just 3)
Just 4
> fmap length (Data.Map.fromList [(1, "hi"), (2, "there")])
fromList [(1,2),(2,5)]
需要注意的是fmap
不允许改变容器的“形”,所以如果比如你fmap
名单,结果有相同数量的元素,如果你fmap
一个Just
不能成为一个Nothing
。 在正式的条款,我们要求fmap id = id
,也就是说,如果你fmap
身份的功能,没有什么变化。
到目前为止,我已经使用术语“容器”过,但它确实比更普遍一点。 例如, IO
也是一个仿函数,而我们在这种情况下,“形”的意思是, fmap
上的IO
动作不应该改变的副作用。 事实上,任何单子是一个仿函数2。
在范畴论,函子允许你不同类别之间进行转换,但在Haskell我们才真正有一类,通常被称为Hask。 因此在Haskell所有仿函数从Hask转换为Hask,所以他们就是我们所说的endofunctors(仿函数从类别本身)。
在最简单的形式,仿函数有点无聊。 只有这么多,你可以只用一个操作做。 然而,一旦你开始添加操作,可以从常规仿函数到应用性函子来单子,事情很快得到了很多更有趣,但这已经超出了这个答案的范围。
1但是Set
则不然,因为它只能存储Ord
类型。 函子必须能够包含任何类型。
2由于历史的原因, Functor
是不是一个超Monad
,尽管很多人认为是应该的。
让我们来看看类型。
Prelude> :i Functor
class Functor f where fmap :: (a -> b) -> f a -> f b
但是,这是什么意思?
首先, f
是一个类型的变量在这里,它代表了一个类型构造: fa
是一种类型; a
是对于某些类型的类型可变的信誉。
其次,提供一个功能g :: a -> b
,你会得到fmap g :: fa -> fb
。 即fmap g
是一个函数,变换型的东西fa
到类型的东西fb
。 请注意,我们不能在类的事情就a
也不b
这里。 该函数g :: a -> b
以某种方式作出对类型的工作的事情fa
,并将它们转换为类型的东西fb
。
请注意, f
是一样的。 只有其他类型的变化。
那是什么意思? 这可能意味着很多东西。 f
通常被看作是东西“容器”。 然后, fmap g
使g
作用于这些容器的内部,而不会破坏他们开放。 结果仍然包含“内部”,该类型类Functor
不为我们提供的能力来打开它们,或者偷看里面。 只是里面不透明的东西有些转型是所有我们得到的。 任何其他功能将不得不从其他地方。
还要注意不说,这些“集装箱”式随身携带的只是一个“东西” a
; 可以有很多不同的“事”,“内部”,但所有相同类型的a
。
最后,对于仿函数任何候选人必须遵守的法律函子 :
fmap id === id
fmap (f . g) === fmap f . fmap g