函数式编程更偏向于输入和输出
interface A{
piblice boolean apply(T input);
piblice boolean equals(Object other);
}
转化scala
用 T => Boolean 表示(是不是很神奇)
表达式:有返回值
语句:可执行,但无返回值
scala中 if 就是一个表达式
scala 大部分语句是返回最后一个表达式的值作为结果
1、Functor 函子T[_] (态射)
面向函数编程 其实 讲的是一种范畴论,即一种转化,从一种范畴转化到另一种范畴,用态射表示其中的映射关系 例如 flatmap和map。
T[_]:
猫[黄色] 猫[红色]
狗[黄色] 狗[红色]
猫 -》 狗 ==== 函子
黄色 -》 红色 === 态射
函子的一个典型应用就是 map 和 flapmap(拆包和解包)
2、Applicative
我们把类似Option这一类值当作封装过的值,Functor可以把这类封装过的值拆包,并执行函数映射。但是,如果 Function 也是被包装过的,Functor还能发挥作用吗?看一个实验:
我们目的是要遍历 op1,让其内部执行 (x:Int) => x * x,也就是把 op1的 数值1 拿出来执行 1 * 1 = 1
op1.map(fop _) 报错了,通过代码也可以看出这是不可行的。而 fop 是一个Option类型,如果执行内部函数应该使用map方法
scala> op1.map(o=>fop.map(f => o))
res2: Option[Option[Int]] = Some(Some(1))
如上虽然通过map函数可以执行到函数但是返回值却不是我们期望的:Option[Option[Int]]。
应该返回 1 Int类型 。OK,Applicative出场。
Applicative的作用是: 某个值去调用封装函数里的函数,如下:
scala> val f1 = (x: Int) => x + 1
f1: Int => Int =
scala> val f2 = (x: Int) => x + 2
f2: Int => Int =
scala> val fs = List(f1, f2) // 对 f1 和 f2 进行封装
fs: List [Int => Int] = List(, )
scala> for(f <- fs; x <- List(1, 2, 3)) yield f(x)
res15: List[Int] = List(2, 3, 4, 3, 4, 5)
List(f1, f2) 映射 List(1, 2, 3),映射之后得到了List(2, 3, 4, 3, 4, 5)
即 封装过的函数 List(f1, f2) 返回了 传一个值,返回了 调用 f1函数 和 f2函数过后的值 (当然也可以加case 模式达到只调用一个函数的目的) 。
3、Monad 单子 ,我理解的就是一个封装了某种行为的一个函数,我们可以将它传来传去。
Monad的典型应用就是flatMap,它封装了压缩操作
比如 List( List(1), List(2) ) flat 之后是 List(1, 2)
Some( Some(2) ) flat之后是 Some(2)
=================================================================
下面的例子实现的功能:创建一个工作流,先读入一个文件,做一些计算,然后写出计算结果
展示了,函子、单子、和 for(工作流---即代码从上到下执行)。
// 注:如果一个类是函子或单子,那就可以用for表达式来操作里面的类型
//我们可以封装某代码片段,定义为一类对象,然后传来传去,
// 或者用某种依赖注入方法,注入到框架。
// 例如,当我们需要一个类似 依次执行的管道而不立即执行的的行为时,使用单子是非常有效的。可以用单子来控制和约束该行为
import scalax.functional.{Applicative,Functor,Monad}
trait ManagedResource[T] {
// 在单子里用到了懒加载,就是说单子只返回了一个含有某种操作的对象,当调用loan函数时才懒加载才触发
def loan[U](f: T => U): U
}
// 文件读、写操作的封装
object ManagedResource {
def readFile(file: java.io.File) = new ManagedResource[java.io.InputStream] {
def loan[U](f: java.io.InputStream => U): U = {
val stream = new java.io.BufferedInputStream(new java.io.FileInputStream(file))
try {
f(stream)// 有括号,函数调用
} finally {
stream.close()
}
}
}
def writeFile(file: java.io.File) = new ManagedResource[java.io.OutputStream] {
def loan[U](f: java.io.OutputStream => U): U = {
val stream = new java.io.BufferedOutputStream(new java.io.FileOutputStream(file))
try {
f(stream)
} finally {
stream.close()
}
}
}
def make[T <: { def close(): Unit }](t: => T): ManagedResource[T] =
new ManagedResource[T] {
def loan[U](f: T => U): U = {
val resource = t
try {
f(resource)
} finally {
resource.close()
}
}
override def toString = "ManagedResource(...)"
}
// 有了函子和单子的隐式实现,scala就可以在上下文中找到它
// 函子的apply方法:当调用loan(借给)方法时,把a “ 借用给 f”
// 函子的map方法:当调用loan(借给)方法时,即调用ma的租借方法,参数数 用mapping包装f后的值
implicit object MRFunctor extends Functor[ManagedResource] {
override def apply[A](a: A) = new ManagedResource[A] {
override def loan[U](f: A => U) = f(a)
override def toString = "ManagedResource("+a+")"
}
override def map[A,B](ma: ManagedResource[A])(mapping: A => B) = new ManagedResource[B] {
override def loan[U](f: B => U) = ma.loan(mapping andThen f)
override def toString = "ManagedResource.map("+ma+")("+mapping+")"
}
}
// 单子的 flatten 方法实现 先调用外层资源的 loan 方法,然后再调用外层资源返回的内部资源的 loan 方法
implicit object MRMonad extends Monad[ManagedResource] {
override def flatten[A](mma: ManagedResource[ManagedResource[A]]): ManagedResource[A] =
new ManagedResource[A] {
override def loan[U](f: A => U): U = mma.loan(ma => ma.loan(f))
override def toString = "ManagedResource.flatten("+mma+")"
}
}
implicit object MRApplicative extends Applicative[ManagedResource] {
override def lift2[A,B](func: ManagedResource[A=>B])(ma: ManagedResource[A]): ManagedResource[B] =
new ManagedResource[B] {
override def loan[U](f: B => U): U = func.loan(n => ma.loan(n andThen f))
override def toString = "ManagedResource.lift2("+func+")("+ma+")"
}
}
}
=========================================================
import scalax.functional.{Applicative, Functor, Monad, Implicits}
import Implicits._
import java.io._
object Example {
type LazyTraversable[T] = collection.TraversableView[T, Traversable[T]]
// 函数是被调用,所以用接受。
// makeLineTraversable 方法接受一个input参数,返回一个 Traversable[String]对象
// foreach 方法调用readLine,直到全部读完。每读出一行,把它喂给匿名函数 f
// 最后调用view方法返回惰性求值的文本行 集合(A[B] 表示 B集合)
// type LazyTraversable[T] = collection.TraversableView[T,Traversable[T]]
def makeLineTraversable(input: BufferedReader) = new Traversable[String] {
def foreach[U](f: String => U): Unit = {
var line = input.readLine()
while (line != null) {
f(line)
line = input.readLine()
}
}
} view
// 函数是被调用,所以用接受。
// getLines方法接受一个 file文件对象,返回一个包含字符串的 ManagedResource 集合
// 用for表达式,表达一个工作流
// 调用 readFile 方法,返回 输入流,并包装成buffered
// 最后把 buffered 传给 makeLineTraversable来构造一个 LazyTraversable[T] 返回。
// 注:如果一个类是函子或单子,那就可以用for表达式来操作里面的类型
def getLines(file: File): ManagedResource[LazyTraversable[String]] =
for {
input <- ManagedResource.readFile(file)
val reader = new InputStreamReader(input)
val buffered = new BufferedReader(reader)
} yield makeLineTraversable(buffered)
// 现在需要逐行读入,并计算每行的长度,计算结果写入一个新文件。
// 下面定一个新工作流来实现
// lineLedngthCount方法接受两个参数,最后将结果写入新文件
// 调用 getLines 返回所有行的 TraversableView
// 然后对每一行调用 length 计算长度,并将结果与行号组合。然后封装流
// 最后 BufferedWriter 写入新文件
//--------------------------------------------------------------------------------------------------
// 注这个方法并不执行任何计算,它只是返回一个 ManagedResource[Unit]
// 这个单子的 loan 方法被调用时才读取、计算、并写入结果。这个工作流只是组合了计算行长度的行为
// 而它并不实际执行。这样就带来了一些灵活性,我们可以将该代码片段,定义为一类对象,然后传来传去,
// 或者用某种依赖注入方法,注入到框架。
// 当我们需要一个类似 依次执行的管道而不立即执行的的行为时,使用单子是非常有效的。可以用单子来控制和约束该行为
def lineLengthCount(inFile: File, outFile: File) =
for {
lines <- getLines(inFile)
val counts = lines.map(_.length).toSeq.zipWithIndex
output <- ManagedResource.writeFile(outFile)
val writer = new OutputStreamWriter(output)
val buffered = new BufferedWriter(writer)
} yield buffered.write(counts.mkString("\n"))
def main(args: Array[String]): Unit = {
workflow(new File("test.in"), new File("test.out")).loan(_ => ())
}
}
网友评论