[关闭]
@MiloXia 2015-08-25T10:00:05.000000Z 字数 3417 阅读 1773

composability

函数式编程


f.g 表达复合函数(组合函数调用),是函数组合的一种形式;
其它语言的 f(g(x)) ,如果是scala的compose(或clojure的comp)则是一样的,别的语言(没有函数)手写f'(x) = f(g(x)) 也是一样的。
但是为什么突然冒出ADT来,我不理解。

组合性
https://en.wikipedia.org/wiki/Function_composition_(computer_science)
其实就是函数可组合的能力
函数组合,则是将多个函数并成一个函数的过程(比如>> 和 . 乃至 monad 此处写做>>=更好)
例子:
id + const

  1. > :t id
  2. id :: a -> a
  3. > :t const
  4. const :: a1 -> b -> a1
  5. > :t const id
  6. const id :: b -> a -> a
  7. > let scd = const id
  8. > scd 1 2
  9. 2
  10. -- >> . 则直接得不到 scd
  11. > :t const >> id
  12. const >> id :: a -> a
  13. > :t const . id
  14. const . id :: b -> b1 -> b
  15. > :t id . const
  16. id . const :: a -> b -> a

组合的价值在于重用,重用就是DRY, 若是这么结束就太无聊了,必须深挖一下这个话题,题主的问题很好啊!下面是我的一些总结:(没啥深奥的,只是安利几条common sense)

pure function
纯函数为函数组合性有无的分界线,非纯函数一定没有组合性,只有纯函数才可组合。(这话是不正确的,但是却很有价值,价值在于鼓励尽量少写带副作用的函数(Haskell里好像问题不大),带副作用的函数组合得依靠monad)

组合性大小
也就是函数组合能力大小,或者是一个函数可重用的可能性大小
多态函数 > 常态函数 (不同语言叫法不一样哇)

  1. > :t id
  2. id :: a -> a
  3. > :t (+)
  4. (+) :: Num a => a -> a -> a
  5. > let add x y = x + y :: Int
  6. > :t add
  7. add :: Int -> Int -> Int

id > (+) > add
函数定义(类型)越抽象,可组合性就越大;显然无拘无束的id是几乎可与任何函数组合的(至于有没有卵用,则就是需求问题了),而(+) > add 也很明显;

函数组合形式(基本组合子)
复合函数确实是很直观的函数组合形式,但是函数组合则比.或>>(同scala的compose和andThen)自由的多,就如上面那个const + id; (其实是一次部分应用),不一定要拘泥于复合函数的形式, 我把.,>>等函数称为函数组合等基本组合子(定义函数组合的基本规则)
-----下面跳到scala描述
函数组合主要来表达函数重用,那么我通过foldRight来实现exists,forAll,filter还有map,flatMap也是函数组合:

  1. def exists(p: A => Boolean): Boolean =
  2. foldRight(false){(a,b) => p(a) || b } //op = (a,b) => p(a) || b
  3. def forAll(p: A => Boolean): Boolean =
  4. foldRight(true){(a,b) => p(a) && b}
  5. def filter(p: A => Boolean): Stream[A] =
  6. foldRight(empty[A]){(h,t) => if(p(h)) cons(h,t) else t}
  7. def map[B](p: A => B): Stream[B] =
  8. foldRight(empty[B]){(h,t) => cons(p(h), t)}
  9. def flatMap_1[B](f: A => Stream[B]): Stream[B] =
  10. foldRight(empty[B]){(h,t) => f(h) #++ t}

以上就是通过foldRight组合子(函数)实现其它组合子的过程,foldRight与各种op的组合,比如map:

  1. def map[B](p: A => B): Stream[B] = {
  2. foldRight(empty[B]){(h,t) => cons(p(h), t)}
  3. }

等价于,以下复合函数的形式

  1. def f[B](op: (A, => Stream[B]) => Stream[B]): Stream[B] = foldRight(empty[B])(op)
  2. def g[B, C >: A](p: C => B)(h: C ,t: => Stream[B]): Stream[B] = cons(p(h), t)
  3. def map_1[B](p: A => B): Stream[B] = (f[B] _ compose g[B, A])(p)
  4. //将foldRight(empty[B])和g组合起来

(filter+ other 又可组合出新的函数)

函数式本身就是一种组合逻辑,需要组合各种基本函数获得更复杂功能更强大的函数的一种设计方式,俗称:自底向上

所以最常见的组合形式,其实就是重用多个函数返回新的函数,只不过引入中间函数(如上面的f,g)(下面称为转换子)可转为f.g的复合函数形式

Monad
Monad本身就是挖几个坑,然后把函数填进去,(看>>=的形式),只举个例子:

  1. def f(x: Int) = Some(x)
  2. def g(y: String) = Some(y)
  3. def fg(x: Int, y: String) = for {
  4. a <- f(x)
  5. b <- g(y)
  6. } yield a + b

至于单子可以组合带副作用的函数,这句话当然是错的,单子只能组合纯函数,但是可以将副作用推迟,或者隔离成纯函数部分以及副作用部分,间接的来说就是组合了非纯函数了,可脑补IO Monad

自定义组合
之前看到过抽象出Process[I, O]类型,并定义|> 方法去组合Process:

def count[I]: Process[I,Int] = lift((i: I) => 1.0 ) |> sum |> lift(_.toInt)

如同>>= 是一种自定义的组合方式

函数类型与组合性
前面提到函数类型定义会关系到组合性的大小,其实函数类型是函数组合的基础;函数组合可认为是一套基于函数类型的代数系统。
前面为何可以用foldRight实现exists,forAll,filter等函数?
看函数类型:

  1. foldRight[B](z: B)(op: (A, => B) => B): B
  2. map[B](p: A => B): Stream[B]
  3. ...

已知map是A => B => Stream[B]的函数,
那么就要把(z: B) => (op: (A, => B) => B) => B
变为(Stream[B]) => ((A, => Stream[B]) => Stream[B]) => Stream[B],
部分应用第一个参数后返回f: ((A, => Stream[B]) => Stream[B]) => Stream[B],
那么g就是(A => B) => ((A , => Stream[B]) => Stream[B]), 返回部分可做op传入,这样g和f就可以组合起来了。
所以,函数类型决定了函数组合方式。

类型同构
(以下把compose 写做.)
如果:
f: A => B
g: C => D
B ~ C (B与C是同构关系)
那么f g可组合,只要引入同构函数fbc, 则:fg = f.fbc.g
同样函数(高阶函数)类型也可做同构
f: A => (B, C)
g: (A => (C, B)) => D
A => (B, C)和A => (C, B)同构,引入同构函数fabc
fg = f.fabc.g

部分应用与柯里化
f: (A, B, C) => D 柯里化为 A => B => C => D
部分应用后可得到
f': B => C => D, f'': C => D ...
f' uncurried为 f: (B, C) => D
如果有两个函数
g: (A , B) => C 与 f: A => B
g2 = g': B => C (g的部分应用)
则 fg' = f.g2 = f.g'

总结
下面构建一套简单代数系统(仅限理解,不可做运算) 来描述函数组合:
1. 定义基本组合子: + 用于组合函数 如: ., >>, >>= 等等
2. 类型同构可看成一种转换子: t
3. 部分应用也是一种转换子: t'
4. 也可以引入任意的中间函数做转换子: t"(可见t和t'为t"的特殊形式)
5. 任何类型的函数f和g,通过应用转换子(t | t' | t")之后可以施用'+'的则称f和g是可组合的:
fg = t|t'|t"(f) + t|t'|t"(g)
(本人数学sense缺乏)

综上, 函数组合的基本组合子是可以有多个的(多种定义), 他们(., >>, >>=)也满足这个代数系统(., >>, >>=本身就是函数),就是也具有组合性, 也可以做转换(至于用.做基本组合子的组合形式能否转为用>>=或别的组合子的形式, 不知道怎么证明)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注