mow*_*ker 6 functional-programming function
在整个夏天,我学到了一些PHP和javascript,所以我想我今年也会在数学方面有一个良好的开端,对我来说,这将是微积分.我正在观看一些视频,并发现了这个:
http://www.youtube.com/watch?v=K6hxKU1kWUs&feature=mfu_in_order&list=UL
他说那样(f+g)(x)=f(x)+g(x).我从来没有见过这样的函数,所以我想我会问这是否也是用编程语言实现的.
假设我有伪代码:
function double(x){
return x*2;
}
function triple(x){
return x*3;
}
Run Code Online (Sandbox Code Playgroud)
是否有任何编程语言可以允许以下内容:
(double + triple)(10)
......等于50?
还有,我可以从一个不是10000年的来源学习微积分吗?
另外,我知道没有编程语言可以使用这种确切的语法,但我的意思是类似的......
在Haskell中,这实际上非常简单:
Prelude Control.Monad> liftM2 (+) (* 2) (* 3) 10
50
Run Code Online (Sandbox Code Playgroud)
或者,或者:
Prelude Control.Applicative> (+) <$> (* 2) <*> (* 3) $ 10
50
Run Code Online (Sandbox Code Playgroud)
或者,更详细地说:
import Control.Monad
main :: IO ()
main = do let (+$) = liftM2 (+) -- Define a new infix operator
let double = (* 2)
let triple = (* 3)
print $ (double +$ triple) 10
Run Code Online (Sandbox Code Playgroud)
这里发生了什么事?如果你的经验在于PHP和JavaScript,Haskell是一种非常陌生的语言,所以我会尝试将其分解.但只是抬头:这里的相关功能(liftM2和<$>/ <*>)比简单的这种用法更为通用,这一点可以使它们理解起来很困难.然而,由此产生的力量是值得的.
这是我对高级摘要的尝试:liftM2采用二进制函数op并生成一个二进制函数,该函数对"更复杂"的值进行操作.如果你的"更复杂"的值是单参数函数,f并且g只有一种很好的方法来组合它们并产生一个新的单参数函数:产生一个函数,它将参数传递x给f和g,然后计算f x `op` g x-it结合它们的结果通过使用op.在JavaScript中,这就像是
function liftM2_functions(op) {
return function (f,g) {
return function (x) { op(f(x), g(x)) }
}
}
Run Code Online (Sandbox Code Playgroud)
由于liftM2只适用于二进制函数,所以还有liftM3三元函数等等,但这很麻烦; 因此,引入了<$>和<*>运算符,这完全等同于.liftMn f a1 a2 ... anf a1 <*> a2 <*> ... <*> an
如果您想要更详细的答案,请继续阅读.我会警告你,我不确定这是多么清楚; 它依赖于我需要半手工的一些概念,而且我可能没有做得很好.但如果你是游戏,我们就去吧.
首先,我们来解释一些Haskell语法:
(* 2)和(* 3)正在编写的速记方式\x -> x*2和\x -> x*3,这只是哈斯克尔的JavaScript的function (x) { return x*2 }和function (x) { return x*3 }.(+)只是执行添加的功能; 它是中缀运算符的前缀形式+.f x是功能应用f(x); f x y被解析为(f x) y,但可以被认为是双参数函数应用程序f(x,y).$缀运算符也是函数应用程序,但优先级较低:... $ x被解析为(...) x.<$>并且<*>是花哨的中缀运营商.所以!有了这个,让我们先考虑第一个片段.在liftM2 (+) (* 2) (* 3) 10,你想要的"功能添加功能"是liftM2 (+); 它的两个论点是(* 2)和(* 3); 和参数的结果即是10.这是怎么回事?在Haskell中,考虑到这一点的正确方法是在类型方面.以下是相关类型 - 但要注意,您可能不会立即理解它们.
liftM2 :: Monad m => (a -> b -> r) -> m a -> m b -> m r
(+) :: Num a => a -> a -> a
(* 2) :: Num a => a -> a
(* 3) :: Num a => a -> a
10 :: Num a => a
Run Code Online (Sandbox Code Playgroud)
这里,f :: t意思是" f有类型t".这些类型非常抽象,所以我们简化一些事情.当你看到Num a,它意味着"对于某种类型a的数字"; 例如,我们可以将其视为Int或Double.这样就给了我们
(+) :: Int -> Int -> Int
(* 2) :: Int -> Int
(* 3) :: Int -> Int
10 :: Int
Run Code Online (Sandbox Code Playgroud)
好吧,10是一个整数.并且怎么样(* 2)和(* 3)?这些是函数,表示为->,它将整数映射到整数.你知道这(+)是一个整数的二元函数,所以你可能认为它有类型(Int,Int) -> Int.但是,在Haskell中,考虑这个的正确方法是作为一个函数,它接受一个整数并返回另一个函数 ; 这叫做currying.在JavaScript中,这将实现为
function add(x) {
return function (y) {
return x + y
}
}
// Usage: add(10)(11) = 21.
Run Code Online (Sandbox Code Playgroud)
你可能不明白为什么这是好的,但它会变得有点相关.
现在,让我们来解决liftM2 :: Monad m => (a -> b -> r) -> m a -> m b -> m r.什么地球是怎么回事?忽略这Monad m一点,这表示liftM2采用双参数函数,"monadic a"和"monadic b",并产生"monadic r".但是 - 感谢currying - 这与liftM2使用双参数函数并返回一个双参数函数相同,其参数和结果是monadic : liftM2 :: Monad m => (a -> b -> r) -> (m a -> m b -> m r). 这就是这里发生的事情:liftM2 (+)是一个添加的monadic版本.
现在,我继续使用monadic这个词,我还没有定义它.我不会去!网上有很多monad教程,有些甚至是好的; 如果你很好奇,请查看"你可以发明Monad!" .这就是你目前需要了解的全部内容:monadic值是以某种方式"增强"的值,通过在它周围有一些额外的结构.由于列表是monad,[1,2,3]因此monad列表中是monadic int; 在那里,结构是它是一个int,可以是一个或两个或三个.我们在这里关注读者monad:在这种情况下,monadic int只是一个函数,它接受一些类型的对象a并返回一个int.这个想法是它是一个整数,可以读取并依赖于某些环境.
所以,我们在这里所拥有的正是如此. (* 2)是一个获取并返回整数的函数.专门研究liftM2使用阅读器monad 的类型,我们最终得到以下结果:
liftM2 :: (a -> b -> r) -> ((e -> a) -> (e -> b) -> (e -> r))
Run Code Online (Sandbox Code Playgroud)
现在这很有希望!该liftM2函数采用二进制函数,并生成一个本身作用于函数的函数.应用它(+) :: Int -> Int -> Int,我们得到
liftM2 (+) :: (e -> Int) -> (e -> Int) -> (e -> Int)
Run Code Online (Sandbox Code Playgroud)
如果你考虑一下,这是函数的添加类型:liftM2 (+)接受两个单参数函数,并产生一个新的单参数函数,并通过添加它们的结果来实现.
现在,如何liftM2实施?像这样(真实实现的重新格式化版本):
liftM2 f m1 m2 = do x1 <- m1
x2 <- m2
return $ f x1 x2
Run Code Online (Sandbox Code Playgroud)
这里发生了什么?这是说"得到类型的值a出来的m1,再弄出来的m2,并与给定结合这些值f".在我们的读者monad(单参数函数)中m1 :: e -> Int,并且x1 <- m1适用m1于"环境",在我们的例子中10.所以在我们的案例中,我们有
do doubled <- (* 2)
tripled <- (* 3)
return $ doubled + tripled
Run Code Online (Sandbox Code Playgroud)
当然,这整个表达必须依赖于神秘的环境,因为我从来没有在任何地方提到它.所以它的类型是Int -> Int:一个带有int,double和三倍的函数,并将两者一起广告.当然,这就是我们开始的地方.
怎么样<$>和<*>?我在上面解释了它们,我可以告诉你基本的想法:<$>就像一个liftM1函数,<*>并将"复杂"的函数应用于"复杂"的值.他们的实际类型是
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)
我不打算在这里详细解释这些,因为我认为我不能做得很好.在一个句子中,仿函数中的一个对象在某种意义上是一个具有额外复杂性的普通值,并且<$>(也称为fmap)单独留下并发症并将函数应用于内部的普通旧值.并且<*>是额外结构的一部分,它允许您同时将函数应用于多个结构内的多个普通值.但是如果你想要更多的信息,它就在那里(虽然我不知道一个好的资源 - 人们经常建议你学习一个Haskell).
通过利用一些强大的构造(applicative functors和/或monad),我们可以基本上免费获得这个功能添加的概念,还有更多.(函数乘法? liftM2 (*).函数级if-then-else? liftM3 if' where if' c t f = if c then t else f等等.)Haskell的优点在于这些高级功能已经完成.Funlude和monad在Prelude中定义,该模块隐式包含在每个Haskell程序中(如Java的java.lang命名空间); 所述Control.Monad模块(定义liftM2)和Control.Applicative模块(它定义应用性函子,<$>和<*>)是标准库的一部分1.Haskell的高阶和静态类型允许它利用这些概念; 在PHP或Java中执行此操作基本上是不可能的,虽然JavaScript可以表达一些概念,但它是动态类型的事实意味着实现必须看起来非常不同.其他语言(如Scala)也可以并且确实利用这些概念,尽管Scala是我唯一可以想到的(并且它们是在Scalaz中提供的,而不是标准库).带回家的消息是,访问这些高性能的抽象允许您编写更简单的代码,而不是更少的代码来执行您想要做的不那么抽象的事情 - 例如函数添加.
1:公平地说,Control.Applicative不符合Haskell 2010标准.但是,它默认包含在GHC中,是迄今为止最常用的Haskell编译器(它是事实上的标准),以及Hugs(一个Haskell解释器,它具有重要用途,但已经失去了GHC),并且到目前为止正如我所知,JHC和YHC(另外两个编译器)也是如此.并且没有什么能阻止不相关的Haskell编译器构建Control.Applicative,因为代码完全符合Haskell 2010.