Home > Archives > Scala基础之For表达式

Scala基础之For表达式

Published on

在实际的开发中,当我们需要多重map和flatMap嵌套时,不妨考虑For表达式。 它基本格式为: for ( seq | option | 实现了map或者是flatMap的数据结构) yield expr

case class People(name: String, isMale: Boolean, children: People*)

object SugarFor extends App {
	val lara = People("Lara", false)
	val bob = People("Bob", true)
	val julie = People("Julie", false, lara, bob)
	val people = List(lara, bob, julie)

	val certainName = for {
		p <- people // generator
		n = p.name // definition
		if (n startsWith "Bob")  // filter also called guard
	} yield n
}

For表达式都会被编译器转换成一些高阶函数的调用(map, flatMap, withFilter)。如果For表达式中没有yield的话,那么它会被转换成withFilter和foreach组合调用。上面代码反编译出来的结果:

/* 
  上面的三种类型分别对应了map, withFilter, map
  不过需要注意的是上面的filter实际上在defnition前面执行的,
  如果去掉yield的话,最后一步就会变成foreach。
*/

 this.$outer.certainName_$eq(
    (List)(
    (TraversableLike)this.$outer.people().
        map(
          new SugarFor..anonfun.4(),          
          List..MODULE$.canBuildFrom()
        )
    ).withFilter(
        new SugarFor..anonfun.5()
    ).map(
        new SugarFor..anonfun.6(),        
        List..MODULE$.canBuildFrom()
    )
);

当同时出现多个seq以及过滤表达式的时候。对于这种情况,有一个简单的通用规则。当Generator表达式数目超过1时,每多一个就会多一个flatMap。总的flatMap个数总是比Generator数目少1。

object ForTest extends App {
    val list = List(1,2,3)
    // 两个generator
    val r1 = for {
    	l1 <- list;
    	l2 <- list if l2 > 2
    } yield {
    	(l1, l2)
    }
    
    val r2 = list.flatMap { x1 =>
    	list.withFilter(_ > 2).map { x2 =>
    		(x1, x2)
    	}
    }
    assert(r1 == r2) // true
}
object ForTest extends App {
	val list = List(1,2,3)
	// 三个generator
	val r1 = for {
		l1 <- list;
		l2 <- list if l2 > 1
		l3 <- list if l3 > 2
	} yield {
		(l1, l2, l3)
	}

	val r2 = list.flatMap { x1 =>
		list.withFilter(_ > 1).flatMap { x2 =>
			list.withFilter(_ > 2).map { x3 =>
				(x1, x2, x3)
			}
		}
	}

	assert(r1 == r2) // true
}

如果Generator过程中出现复杂的模式。一般情况下,Generator左边的表达式一般是单一变量x或者是tuple。但如果左边是一个比较复杂的模式,那么编译器会转换成如下格式:

    for (pat <- expr1) yield expr2
    
    // 只有在通过模式匹配的情况下,才会继续执行
    expr1 withFilter {
        case pat => true
        case _ => false
    } map {
        case pat => expr2
    }
import net.liftweb.mongodb.JsonFormats
object ForTest extends App with JsonFormats {
	implicit val jValueformate = allFormats
	val pet1 = Pet("stella", 23)
	val pet2 = Pet("dodge", 12)
	
	val petJValue = (pet1 :: pet2 :: Nil).map(Extraction.decompose)

	val r1 = for {
		JObject(
            JField("name", JString(name)) :: 
            JField("age", JInt(age)) :: 
            Nil
        ) <- petJValue
	} yield {
		(name, age.intValue)
	}

	
	val r2 = petJValue.withFilter {
		case JObject(
            JField("name", JString(name)) :: 
            JField("age", JInt(age)) :: 
        Nil) => true
		case _ => false
	}.map {
		case JObject(
            JField("name", JString(name)) :: 
            JField("age", JInt(age)) :: 
        Nil) => (name, age.intValue)
	}

	assert(r1 == r2) // true
}

通过上面的例子,我们可以得出如下结论:

(1) For表达式可以被转化成map, flatMap, withFilter等高阶函数的调用,反过来map, flatMap, filter也能够通过for来实现(因为withFilter并不是简单的filter所以不能直接实现)

class HighOrderFuncViaFor {
    def map[A, B](list: List[A], f: A => B): 
    List[B] = for { l  <- list } yield f(l)
    
    def flatMap[A, B](list: List[A], f: A => List[B]): 
    List[B] = for { l <- list;l1 <- f(l) } yield l1
    
    def filter[A](list: List[A], f: A => Boolean): 
    List[A] = for { l <- list if f(l) } yield l
}

(2) 只要定义了map, flatMap, withFilter等高阶函数的数据结构都可以进行for表达式(排除没有yield的情况)。

具体的规则如下,针对给定的数据类型F:

> 如果F只定义了map操作,那么对应的For只允许单个的Generator操作,因为前面提到过两个及以上的Generator就会涉及到flatMap。

> 如果同时定义了map和flatMap,那么就能进行多个Generator的操作。

> 如果定义了withFilter方法,那么就会支持以if开头的过滤表达式。

abstract class ADT[A] {
	def map[B](f: A => B): ADT[B]

	def flatMap[B](f: A => ADT[B]): ADT[B]

	def foreach(f: A => Unit): Unit

	def withFilter(f: A => Boolean): ADT[A]
}

声明: 本文采用 BY-NC-SA 授权。转载请注明转自: Allen写字的地方