泛函编程(14)-try to map them all

     虽然明白泛函编程风格中最重要的就是对一个管子里的元素进行操作。这个管子就是这么一个东西:F[A],我们说F是一个针对元素A的高阶类型,其实F就是一个装载A类型元素的管子,A类型是相对低阶,或者说是基础的类型。泛函编程风格就是在F内部用对付A类的函数对里面的元素进行操作。但在之前现实编程中确总是没能真正体会这种编程模式畅顺的用法:到底应该在哪里用?怎么用?可能内心里还是没能摆脱OOP的思维方式吧。在前面Stream设计章节里,我们采用了封装形式的数据结构设计,把数据结构uncons放进了特质申明里:

  trait Stream[+A] {
  	def uncons: Option[(A, Stream[A])]
  	def isEmpty: Boolean = uncons.isEmpty
  }
  object Stream {
  	def empty[A]: Stream[A] = new Stream[A] {
  		def uncons = None
  	}
  	def cons[A](h: => A, t: => Stream[A]): Stream[A] = new Stream[A] {
  		def uncons = Some((h,t))
  	}
  	def apply[A](as: A*): Stream[A] = {
  		if (as.isEmpty) empty
  		else cons(as.head, apply(as.tail: _*))
  	}
  	
  }

我们用tuple(A, Stream[A])来代表一个完整的Stream并把它放进一个Option里,本意是空的Stream就可以用None来表示。这个Option就像是那个附加的套子把我们的目标类型(A, Stream[A])套成了F[A]类型。其实我们的目的是对管子里的A类型进行操作,特别是对A类型元素进行模式匹配。但是在之前的设计里我们却对F[A]这个戴着套子的类型进行了模式匹配。静下来回顾一下觉着还是必须想办法尽量多用些泛函的方式来做。

先看看这个map函数,我们在前面曾经为Option编写了这个函数:(oa:Option[A]).map[B](f: A => B): Option[B]。我们可以向map传入一个操作A级别类型的函数,比如一段A级别类型的模式匹配方式代码。Option map返回的结果是Option[B],是一个高阶类型,但我们可以很方便的用getOrElse来取得这个返回Option里面的元素。看个例子比较一下:

  	//戴着套子进行模式匹配
  	def toList: List[A] = uncons match {
  		case None => Nil
  		case Some((h,t)) => h :: t.toList
  	}
        //用map操作
  	def toList: List[A] = uncons.map {
  		case (h,t) => h :: t.toList
  	} getOrElse(Nil)

从以上例子可以看出:通过使用map,用元素类型级别模式匹配,然后用getOrElse取出。Stream为空时采用getOrElse默认值。可以让代码更简洁易名。
看多几个例子:

    //戴着套子
    def take(n: Int): Stream[A] = {
      if ( n == 0 ) empty
      else
       uncons match {
    	   case None => empty
    	   case Some((h,t)) => cons(h,t.take(n-1))
    	}
    }
    //用map操作
    def take(n: Int): Stream[A] = {
      if ( n == 0 ) empty
      else
       uncons map {
    	   case (h,t) => cons(h,t.take(n-1))
    	} getOrElse(empty)
    }
    //戴着套子
    def takeWhile(f: A => Boolean): Stream[A] =  {
    	uncons match {
    		case None => empty
    		case Some((h,t)) => if ( f(h) ) cons(h,t.takeWhile(f)) else empty
    	}
    }
    //用map操作
    def takeWhile(f: A => Boolean): Stream[A] =  {
    	uncons map {
    		case (h,t) => if ( f(h) ) cons(h,t.takeWhile(f)) else empty
    	} getOrElse empty
    }
    //高阶类型操作
    def foldRight[B](z: B)(op: (A, => B) => B): B = {
    	uncons match {
    		case None => z
    		case Some((h,t)) => op(h,t.foldRight(z)(op))
    	}
    }
    //monadic style
    def foldRight[B](z: B)(op: (A, => B) => B): B = {
    	uncons map {
    		case (h,t) => op(h,t.foldRight(z)(op))
    	} getOrElse z
    }

嗯,改变操作方式时共性很明显。
再看看下面的例子,如果不用map的话会是多么的混乱:

    //没用map方式
    def unfold[A,S](z: S)(f: S => Option[(A,S)]): Stream[A] ={
    	f(z) match {
    		case None => empty
    		case Some((a,s)) => cons(a,unfold(s)(f))
    	}
    }
    def mapByUnfold[B](f: A => B): Stream[B] = {
			unfold(uncons) {
    		case Some((h,t)) => Some((f(h),Some((t.headOption.getOrElse(h), t.tail.tailOption.getOrElse(empty)))))
    		case _ => None
    	}
    }
		def zipWithByUnfold[B,C](b: Stream[B])(f: (A,B) => C): Stream[C] = {
			unfold((uncons,b.uncons)) {
				case (Some((ha,ta)),Some((hb,tb))) => Some(f(ha,hb),(Some((ta.head,ta.tail)),Some((tb.head,tb.tail))))
				case _ => None
			}
		}

看上面这些代码,由于传入unfold的函数f的返回结果是个高阶类型Option,这使得整体表达形式不但臃肿,更乱还很难看得懂。试着用map改写这些函数:

    def unfoldWithMap[A,S](z: S)(f: S => Option[(A,S)]): Stream[A] ={
    	f(z) map {
    		case (a,s) => cons(a,unfold(s)(f))
    	} getOrElse empty
    }
    def mapByUnfoldWithMap[B](f: A => B): Stream[B] = {
		unfold(this) { s =>
			this.uncons map {
    			    case (h,t) => (f(h),t)
 			}
    	}
    }

看起来简洁多了。另外一个用了flatMap:

 		def zipWithByUnfoldWithMap[B,C](b: Stream[B])(f: (A,B) => C): Stream[C] = {
 		//起始状态是tuple(Stream[A],Stream[B]),状态转换函数>>> (s1,s2) => Option(a, (s1,s2))
			unfold((this,b)) { s => {
			  for {
			  	a <- s._1.uncons   //用flatMap从Option[(A,Stream[A])]取出元素 >>> (A,Stream[A])
			  	b <- s._2.uncons   //用flatMap从Option[(B,Stream[B])]取出元素 >>> (B,Stream[B])
			  } yield {
			     ( f(a._1, b._1), (a._2, b._2) ) //返回新的状态:C >>> (f(a,b),(ta,tb))
			    }
			 }
		 }
		}

乍看起来好像挺复杂,但尝试去理解代码的意义,上面一段代码会更容易理解一点。
中间插播了一段map,flatMap的示范,目的是希望在后面的设计思考中向泛函编程风格更靠近一点。

Python泛函编程是一种编程范式,它主要关注于使用高阶函数和不可变数据来构建程序。泛函编程的核心思想是将计算过程看作是函数之间的转换,通过组合和应用函数来解决问题。 在Python中,泛函编程可以通过以下几个方面来实现: 1. 高阶函数:Python中的函数是一等公民,可以作为参数传递给其他函数,也可以作为返回值。高阶函数可以接受一个或多个函数作为参数,并返回一个新的函数。常见的高阶函数包括map、filter和reduce等。 2. 匿名函数:Python中的lambda表达式可以用来创建匿名函数,这些函数通常用于简单的计算或作为其他函数的参数。 3. 不可变数据:在泛函编程中,数据被视为不可变的,即不能被修改。这样可以避免副作用,使得程序更加可靠和易于理解。 4. 列表推导式:列表推导式是一种简洁的语法,可以通过对一个列表进行转换或筛选来创建一个新的列表。它可以替代循环语句,使代码更加简洁和易读。 5. 函数组合:泛函编程鼓励将多个函数组合在一起,形成一个新的函数。这样可以将复杂的问题分解为多个简单的函数,提高代码的可读性和可维护性。 6. 惰性求值:泛函编程中的惰性求值指的是只在需要的时候才进行计算,可以提高程序的效率。Python中的生成器和迭代器就是惰性求值的一种实现方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值