必威-必威-欢迎您

必威,必威官网企业自成立以来,以策略先行,经营致胜,管理为本的商,业推广理念,一步一个脚印发展成为同类企业中经营范围最广,在行业内颇具影响力的企业。

函数式编程模式的运行方式以递归算法为主,我

2019-09-21 22:33 来源:未知

很多时候我们会遇到一些高阶类型F[_],但又无法实现它的map函数,也就是虽然形似但F不可能成为Functor。看看下面的例子:

  cats是scala的一个新的函数式编程工具库,其设计原理基本继承了scalaz:大家都是haskell typeclass的scala版实现。当然,cats在scalaz的基础上从实现细节、库组织结构和调用方式上进行了一些优化,所以对用户来说:cats的基础数据类型、数据结构在功能上与scalaz是大致相同的,可能有一些语法上的变化。与scalaz著名抽象、复杂的语法表现形式相比,cats的语法可能更形象、简单直白。在scalaz的学习过程中,我们了解到所谓函数式编程就是monadic Programming:即用monad这样的数据类型来构建程序。而实际可行的monadic programming就是用Free-Monad编程了。因为Free-Monad程序是真正可运行的,或者说是可以实现安全运行的,因为它可以保证在固定的堆栈内实现无限运算。我们知道:函数式编程模式的运行方式以递归算法为主,flatMap函数本身就是一种递归算法。这就预示着monadic programming很容易造成堆栈溢出问题(StackOverflowError)。当我们把普通的泛函类型F[A]升格成Free-Monad后就能充分利用Free-Monad安全运算能力来构建实际可运行的程序了。由于我们在前面已经详细的了解了scalaz的大部分typeclass,包括Free,对cats的讨论就从Free开始,聚焦在cats.Free编程模式方面。同时,我们可以在使用cats.Free的过程中对cats的其它数据类型进行补充了解。

在前面的讨论里我们提到自由数据结构就是产生某种类型的最简化结构,比如:free monoid, free monad, free category等等。我们也证明了List[A]是个free monoid。我们再看看free monad结构Free的定义:scalaz/Free.scala

    在前面的讨论里我们提到自由数据结构就是产生某种类型的最简化结构,比如:free monoid, free monad, free category等等。我们也证明了List[A]是个free monoid。我们再看看free monad结构Free的定义:scalaz/Free.scala

trait Interact[A]case class Ask(prompt: String) extends Interact[String]case class Tell(msg: String) extends Interact[Unit]

cats.Free的类型款式如下:

/** A free operational monad for some functor `S`. Binding is done using the heap instead of the stack,  * allowing tail-call elimination. */sealed abstract class Free[S[_], A] {...  /** Return from the computation with the given value. */  private[scalaz] case class Return[S[_], A] extends Free[S, A]  /** Suspend the computation with the given suspension. */  private[scalaz] case class Suspend[S[_], A](a: S[Free[S, A]]) extends Free[S, A]...

 

Interact类型只容许两种实例:Ask继承了Interact[String], Tell继承Interact[Unit]。我们无法获取map[A,B]函数的B值,因而无法实现map函数了。但是,往往在一些场合里我们需要把F当做Functor来使用,如用Free Structure把F升格成Monad。也就是说我们需要把Interact当做Functor才能构建一个基于Interact的Free Monad。Scalaz里的Coyoneda与任何F[_]类型成同构,而Coyoneda是个Functor,这样我们可以用Coyoneda来替代F。在上面的例子里我们只要得出F的Coyoneda,然后我们就可以用这个Coyoneda来替代F,因为它们是同构的。我们来看看Coyoneda的定义:scalaz/Coyoneda.scala

sealed abstract class Free[S[_], A] extends Product with Serializable {...}

我们在上一篇里证明过Free就是free monad,因为Free是个Monad而且它的结构是最简单的了:

/** A free operational monad for some functor `S`. Binding is done using the heap instead of the stack,
  * allowing tail-call elimination. */
sealed abstract class Free[S[_], A] {
...
  /** Return from the computation with the given value. */
  private[scalaz] case class Return[S[_], A](a: A) extends Free[S, A]

  /** Suspend the computation with the given suspension. */
  private[scalaz] case class Suspend[S[_], A](a: S[Free[S, A]]) extends Free[S, A]
...
sealed abstract class Coyoneda[F[_], A] { coyo =>  /** The pivot between `fi` and `k`, usually existential. */  type I  /** The underlying value. */  val fi: F[I]  /** The transformer function, to be lifted into `F` by `run`. */  val k: I => A...  /** Like `lift.map`. */  def apply[F[_], A, B](_k: A => B): Aux[F, B, A] =    new Coyoneda[F, B]{      type I = A      val k = _k      val fi = fa    }...  type Aux[F[_], A, B] = Coyoneda[F, A] {type I = B}  /** `F[A]` converts to `Coyoneda[F,A]` for any `F` */  def lift[F[_],A]: Coyoneda[F, A] = apply(identity[A])

S是个高阶类,就是一种函数式运算。值得注意的是:现在S不需要是个Functor了。因为Free的一个实例Suspend类型是这样的:

1、Free[S[_],A]可以代表一个运算

 

即使F不是Functor,我们还是可以把F[A]拆成Coyoneda[F,A]。而Coyoneda和F同构,看下面scalaz里的代码:

/** Suspend the computation with the given suspension. */
  private final case class Suspend[S[_], A](a: S[A]) extends Free[S, A]

2、case class Return是一个数据结构。Return代表运算结束,运算结果a存放在结构中。另一个意义就是Monad.point,把一个任意A值a升格成Free

我们在上一篇里证明过Free就是free monad,因为Free是个Monad而且它的结构是最简单的了:

  type CoyonedaF[F[_]] = ({type A[α] = Coyoneda[F, α]})  import Isomorphism._  def iso[F[_]: Functor]: CoyonedaF[F]#A <~> F =    new IsoFunctorTemplate[CoyonedaF[F]#A, F] {      def from[A] = lift      def to[A](fa: Coyoneda[F, A]) = fa.run    }

我们不需要map就可以把F[A]升格成Free

3、case class Suspend也是另一个数据结构。Suspend(a: S[Free[S,A]])代表把下一个运算存放在结构中。如果用Monad.join(a: F[F[A]])表示,那么里面的F[A]应该是个Free[S,A],这样我们才可能把运算结束结构Return[S,A]存放到Suspend中来代表下一步结束运算。

1、Free[S[_],A]可以代表一个运算

我们自己同样可以用更简单的方法来证明:

/**
   * Suspend a value within a functor lifting it to a Free.
   */
  def liftF[F[_], A](value: F[A]): Free[F, A] = Suspend(value)

注意,Suspend(a: S[Free[S,A]])是个递归类型,S必须是个Functor,但不是任何Functor,而是map over Free[S,A]的Functor,也就是运算另一个Free[S,A]值。如果这个Free是Return则返回运算结果,如果是Suspend则继续递归运算。

2、case class Return是一个数据结构。Return(a:A)代表运算结束,运算结果a存放在结构中。另一个意义就是Monad.point(a:A),把一个任意A值a升格成Free

 1 object proof_coyo { 2  trait _Coyoneda[F[_],A] { 3   type I 4   def k: I => A 5   def fi: F[I] 6  } 7  8  def toCoyo[F[_],A] = 9     new _Coyoneda[F, A] {10       type I = A11       val k =  => a12       val fi = fa13     }14  def fromCoyo[F[_]: Functor,A](coyo: _Coyoneda[F,A]): F[A] =15     Functor[F].map16    17 }

 我们在scalaz.Free的讨论中并没能详尽地分析在什么情况下S[_]必须是个Functor。下面我们需要用一些篇幅来解析。

简单来说Free是一个把Functor S[_]升格成Monad的产生器。我们可以用Free.liftF函数来把任何一个Functor升格成Monad。看看Free.liftF的函数款式就知道了:

3、case class Suspend也是另一个数据结构。Suspend(a: S[Free[S,A]])代表把下一个运算存放在结构中。如果用Monad.join(a: F[F[A]])表示,那么里面的F[A]应该是个Free[S,A],这样我们才可能把运算结束结构Return[S,A](a:A)存放到Suspend中来代表下一步结束运算。

对于任何类型F及A, 我们通过toCoyo, fromCoyo可以证明_Coyoneda和F[A]同构。 既然Coyoneda和F[A]同构,那么我们可以这样表述:F[A] >>> Coyoneda[F,A]。也就是说我们可以在任何地方用Coyoneda[F,A]替代F[A]。上面例子中的Interact也可以用Coyoneda替代:

Free程序的特点是算式(description)/算法(implementation)关注分离(separation of concern):我们用一组数据类型来模拟一种编程语句ADT(algebraic data type),这一组ADT就形成了一种定制的编程语言DSL(domain specific language)。Free的编程部分就是用DSL来描述程序功能(description of purpose),即算式了。算法即用DSL描述的功能的具体实现,可以有多种的功能实现方式。我们先看个简单的DSL:

  /** Suspends a value within a functor in a single step. Monadic unit for a higher-order monad. */  def liftF[S[_], A](value: => S[A])(implicit S: Functor[S]): Free[S, A] =    Suspend(S.map(Return[S, A]))

注意,Suspend(a: S[Free[S,A]])是个递归类型,S必须是个Functor,但不是任何Functor,而是map over Free[S,A]的Functor,也就是运算另一个Free[S,A]值。如果这个Free是Return则返回运算结果,如果是Suspend则继续递归运算。

1 trait Interact[A]2 case class Ask(prompt: String) extends Interact[String]3 case class Tell(msg: String) extends Interact[Unit]4 5 type coyoInteract[A] = Coyoneda[Interact,A]
 1 import cats.free._
 2 import cats.Functor
 3 object catsFree {
 4   object ADTs {
 5     sealed trait Interact[+A]
 6     object Interact {
 7       case class Ask(prompt: String) extends Interact[String]
 8       case class Tell(msg: String) extends Interact[Unit]
 9       
10       def ask(prompt: String): Free[Interact,String] = Free.liftF(Ask(prompt))
11       def tell(msg: String): Free[Interact,Unit] = Free.liftF(Tell(msg))
12 
13 
14       implicit object interactFunctor extends Functor[Interact]  {
15         def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ???
16       /*   ia match {
17            case Ask(p) => ???
18            case Tell(m) => ???
19         } */
20       }  
21     }
22   }
23   object DSLs {
24     import ADTs._
25     import Interact._
26     val prg: Free[Interact,Unit] = for {
27       first <- ask("What's your first name?")
28       last <- ask("What's your last name?")
29       _ <- tell(s"Hello $first $last")
30     } yield()
31   }

liftF可以把一个S[A]升格成Free[S,A]。我们用个例子来证明:

简单来说Free是一个把Functor S[_]升格成Monad的产生器。我们可以用Free.liftF函数来把任何一个Functor升格成Monad。看看Free.liftF的函数款式就知道了:

在这个例子里Interact并不是一个Functor,因为我们无法获取Interact Functor实例的map函数。先让我们分析一下Functor的map:

 1 package Exercises 2 import scalaz._ 3 import Scalaz._ 4 import scala.language.higherKinds 5 import scala.language.implicitConversions 6 object freelift { 7 trait Config[+A] { 8   def get: A 9 }10 object Config {11   def apply[A]: Config[A] = new Config[A] { def get = a}12   implicit val configFunctor = new Functor[Config] {13      def map[A,B](ca: Config[A])(f: A => B) = Config[B](f(ca.get))14   }15 }16 17 val freeConfig = Free.liftF(Config("hi config"))  //> freeConfig  : scalaz.Free[Exercises.freelift.Config,String] = Suspend(Exercises.freelift$Config$$anon$2@d70c109)
  /** Suspends a value within a functor in a single step. Monadic unit for a higher-order monad. */
  def liftF[S[_], A](value: => S[A])(implicit S: Functor[S]): Free[S, A] =
    Suspend(S.map(value)(Return[S, A]))
1      implicit object interactFunctor extends Functor[Interact]  {
2         def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ia match {
3            case Ask(p) => ???
4            case Tell(m) => ???
5         }
6       }

在上面的例子里Config是个运算A值的Functor。我们可以用Free.liftF把Config升格成Free[Config,String]。实际上我们可以把这个必须是Functor的门槛取消,因为用Coyoneda就可以把任何F[A]拆解成Coyoneda[F,A],而Coyoneda天生是个Functor。我们先看个无法实现map函数的F[A]:

liftF可以把一个S[A]升格成Free[S,A]。我们用个例子来证明:

map的作用是用一个函数A => B把F[A]转成F[B]。也就是把语句状态从F[A]转成F[B],但在Interact的情况里F[B]已经是明确的Interact[Unit]和Interact[String]两种状态,而map的f是A => B,在上面的示范里我们该如何施用f来获取这个Interact[B]呢?从上面的示范里我们观察可以得出Ask和Tell这两个ADT纯粹是为了模拟ask和tell这两个函数。ask和tell分别返回Free版本的String,Unit结果。可以说:Interact并没有转换到下一个状态的要求。那么假如我们把ADT调整成下面这样呢:

1 trait Interact[+A]  //Console交互2 //println(prompt: String) then readLine 返回String3 case class Ask(prompt: String) extends Interact[String]4 //println(msg: String) 不反回任何值5 case class Tell(msg: String) extends Interact[Unit]
 1 package Exercises
 2 import scalaz._
 3 import Scalaz._
 4 import scala.language.higherKinds
 5 import scala.language.implicitConversions
 6 object freelift {
 7 trait Config[+A] {
 8   def get: A
 9 }
10 object Config {
11   def apply[A](a: A): Config[A] = new Config[A] { def get = a}
12   implicit val configFunctor = new Functor[Config] {
13      def map[A,B](ca: Config[A])(f: A => B) = Config[B](f(ca.get))
14   }
15 }
16 
17 val freeConfig = Free.liftF(Config("hi config"))  //> freeConfig  : scalaz.Free[Exercises.freelift.Config,String] = Suspend(Exercises.freelift$Config$$anon$2@d70c109)
 1       sealed trait FunInteract[NS]
 2       object FunInteract {
 3         case class FunAsk[NS](prompt: String, onInput: String =>  NS) extends FunInteract[NS]
 4         case class FunTell[NS](msg: String, ns: NS) extends FunInteract[NS]
 5         
 6         def funAsk(prompt: String): Free[FunInteract,String] = Free.liftF(FunAsk(prompt,identity))
 7         def funAskInt(prompt: String): Free[FunInteract,Int] = Free.liftF(FunAsk(prompt,_.toInt))
 8         def funTell(msg: String): Free[FunInteract,Unit] = Free.liftF(FunTell(msg,()))
 9         
10         implicit object funInteract extends Functor[FunInteract] {
11            def map[A,NS](fa: FunInteract[A])(f: A => NS) = fa match {
12               case FunAsk(prompt,input) => FunAsk(prompt,input andThen f)
13               case FunTell(msg,ns) => FunTell(msg,f(ns))
14            }
15         }
16       }

由于Ask和Tell都不会返回泛类值,所以无需或者无法实现map函数,Interact[A]是个不是Functor的高阶类。我们必须把它转成Coyoneda提供给Free产生Monad:

在上面的例子里Config是个运算A值的Functor。我们可以用Free.liftF把Config(String)升格成Free[Config,String]。实际上我们可以把这个必须是Functor的门槛取消,因为用Coyoneda就可以把任何F[A]拆解成Coyoneda[F,A],而Coyoneda天生是个Functor。我们先看个无法实现map函数的F[A]:

现在这两个ADT是有类型参数NS的了:FunAsk[NS],FunTell[NS]。NS代表了ADT当前类型,如FunAsk[Int]、FunTell[String]...,现在这两个ADT都通过类型参数NS变成了可map的对象了,如FunAsk[String] >>> FunAsk[String], FunAsk[String] >>> FunAsk[Int]...。所以我们可以很顺利的实现object funInteract的map函数。但是,一个有趣的现象是:为了实现这种状态转换,如果ADT需要返回操作结果,就必须具备一个引领状态转换的机制,如FunAsk类型里的onInput: String => NS:它代表funAsk函数返回的结果可以指向下一个状态。新增函数funAskInt是个很好的示范:通过返回的String结果将状态转换到FunAsk[Int]状态。函数funTell不返回结果,所以FunTell没有状态转换机制。scalaz旧版本Free.Suspend的类型款式是:Suspend[F[Free,A]],这是一个递归类型,内部的Free代表下一个状态。由于我们必须用F.map才能取出下一个状态,所以F必须是个Functor。我们应该注意到如果ADT是Functor的话会造成Free程序的冗余代码。既然cats.Free对F[A]没有设置Functor门槛,那么我们应该尽量避免使用Functor。

1 Free.liftFC(Tell("hello"))                        //> res0: scalaz.Free.FreeC[Exercises.freelift.Interact,Unit] = Suspend(scalaz.Coyoneda$$anon$22@71423665)2 Free.liftFC(Ask("how are you"))                   //> res1: scalaz.Free.FreeC[Exercises.freelift.Interact,String] = Suspend(scalaz.Coyoneda$$anon$22@20398b7c)
1 trait Interact[+A]  //Console交互
2 //println(prompt: String) then readLine 返回String
3 case class Ask(prompt: String) extends Interact[String]
4 //println(msg: String) 不反回任何值
5 case class Tell(msg: String) extends Interact[Unit]

得出对ADT类型要求结论后,我们接着示范cats的Free编程。下面是Free程序的功能实现interpret部分(implementation):

我们看看liftFC函数定义:

由于Ask和Tell都不会返回泛类值,所以无需或者无法实现map函数,Interact[A]是个不是Functor的高阶类。我们必须把它转成Coyoneda提供给Free产生Monad:

1     import ADTs._
2     object iconsole extends (Interact ~> Id) {
3       def apply[A](ia: Interact[A]): Id[A] = ia match {
4          case Ask(p) => {println(p); readLine}
5          case Tell(m) => println(m)
6       }
7     }
8   }
  /** A version of `liftF` that infers the nested type constructor. */  def liftFU[MA](value: => MA)(implicit MA: Unapply[Functor, MA]): Free[MA.M, MA.A] =    liftF)  /** A free monad over a free functor of `S`. */  def liftFC[S[_], A]: FreeC[S, A] =    liftFU(Coyoneda lift s)
1 Free.liftFC(Tell("hello"))                        //> res0: scalaz.Free.FreeC[Exercises.freelift.Interact,Unit] = Suspend(scalaz.Coyoneda$$anon$22@71423665)
2 Free.liftFC(Ask("how are you"))                   //> res1: scalaz.Free.FreeC[Exercises.freelift.Interact,String] = Suspend(scalaz.Coyoneda$$anon$22@20398b7c)

DSL程序的功能实现就是把ADT F[A]对应到实际的指令集G[A],在Free编程里用NaturalTransformation ~>来实现。注意G[A]必须是个Monad。在上面的例子里对应关系是:Interact~>Id,代表直接对应到运算指令println和readLine。我们也可以实现另一个版本: 

Coyoneda lift s 返回结果Coyoneda[S,A], liftFU用Unapply可以把Coyoneda[S,A]转化成S[A]并提供给liftF。看看Unapply这段:

我们看看liftFC函数定义:

 1     type Prompt = String
 2     type Reply = String
 3     type Message = String
 4     type Tester[A] = Map[Prompt,Reply] => (List[Message],A)
 5     object tester extends (Interact ~> Tester) {
 6       def apply[A](ia: Interact[A]): Tester[A] = ia match {
 7         case Ask(p) => { m => (List(), m(p)) }
 8         case Tell(m) => { _ => (List(m), ()) }
 9       }
10     }
11     import cats.Monad
12     implicit val testerMonad = new Monad[Tester] {
13       override def pure[A](a: A): Tester[A] = _ => (List(),a)
14       override def flatMap[A,B](ta: Tester[A])(f: A => Tester[B]): Tester[B] = m => {
15         val (o1,a1) = ta(m)
16         val (o2,a2) = f(a1)(m)
17         (o1 ++ o2, a2)
18       }
19       override def tailRecM[A,B](a: A)(f: A => Tester[Either[A,B]]): Tester[B] =
20          defaultTailRecM(a)(f)
21     }
22   }
  /**Unpack a value of type `M0[A0, B0]` into types `[a]M0[a, B0]` and `A`, given an instance of `TC` */  implicit def unapplyMAB1[TC[_[_]], M0[_, _], A0, B0](implicit TC0: TC[({type λ[α] = M0[α, B0]})#λ]): Unapply[TC, M0[A0, B0]] {    type M[X] = M0[X, B0]    type A = A0  } = new Unapply[TC, M0[A0, B0]] {    type M[X] = M0[X, B0]    type A = A0    def TC = TC0    def leibniz = refl  }  /**Unpack a value of type `M0[A0, B0]` into types `[b]M0[A0, b]` and `B`, given an instance of `TC` */  implicit def unapplyMAB2[TC[_[_]], M0[_, _], A0, B0](implicit TC0: TC[({type λ[α] = M0[A0, α]})#λ]): Unapply[TC, M0[A0, B0]] {    type M[X] = M0[A0, X]    type A = B0  } = new Unapply[TC, M0[A0, B0]] {    type M[X] = M0[A0, X]    type A = B0    def TC = TC0    def leibniz = refl  }
  /** A version of `liftF` that infers the nested type constructor. */
  def liftFU[MA](value: => MA)(implicit MA: Unapply[Functor, MA]): Free[MA.M, MA.A] =
    liftF(MA(value))(MA.TC)

  /** A free monad over a free functor of `S`. */
  def liftFC[S[_], A](s: S[A]): FreeC[S, A] =
    liftFU(Coyoneda lift s)

上面是个模拟测试:我们用个Map[K,V]来模拟互动,K模拟问prompt,V模拟获取回答Input。测试方式是个Function1,输入测试数据Map,在List[Message]里返回所有Tell产生的信息。上面提到过Tester[A]必须是个Monad,所以我们实现了Tester的Monad实例testMonad。实际上 m=>(List,a)就是个writer函数。所谓的Writer就是包嵌一个对值pair(L,V)的Monad,L代表Log,V代表运算值。Writer的特性就是log所有V的运算过程。我们又可以用Writer来实现这个tester:

好了。但是又想了想,一个是Functor的Interact又是怎样的呢?那Ask必须返回一个值,而这个值应该是个Free,实际上是代表下一个运算:

Coyoneda lift s 返回结果Coyoneda[S,A], liftFU用Unapply可以把Coyoneda[S,A]转化成S[A]并提供给liftF。看看Unapply这段:

 1    import cats.data.WriterT
 2     type WF[A] = Map[Prompt,Reply] => A
 3     type WriterTester[A] = WriterT[WF,List[Message],A]
 4     def testerToWriter[A](f: Map[Prompt,Reply] => (List[Message],A)) =
 5     WriterT[WF,List[Message],A](f)
 6     object testWriter extends (Interact ~> WriterTester) {
 7       import Interact._
 8       def apply[A](ia: Interact[A]): WriterTester[A] = ia match {
 9         case Ask(p) => testerToWriter(m => (List(),m(p)))
10         case Tell(m) => testerToWriter(_ => (List(m),()))
11       }
12     }
 1 package Exercises 2 import scalaz._ 3 import Scalaz._ 4 import scala.language.higherKinds 5 object interact { 6 trait Interact[+A]  7 case class Ask[Next](prompt: String, n: String => Next) extends Interact[Next] 8 case class Tell[Next](msg: String, n: Next) extends Interact[Next] 9 10 object interFunctor extends Functor[Interact] {11   def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ia match {12       case Tell => Tell13       case g: Ask[A] => Ask[B](g.prompt, g.n andThen f)14   }15 }
  /**Unpack a value of type `M0[A0, B0]` into types `[a]M0[a, B0]` and `A`, given an instance of `TC` */
  implicit def unapplyMAB1[TC[_[_]], M0[_, _], A0, B0](implicit TC0: TC[({type λ[α] = M0[α, B0]})#λ]): Unapply[TC, M0[A0, B0]] {
    type M[X] = M0[X, B0]
    type A = A0
  } = new Unapply[TC, M0[A0, B0]] {
    type M[X] = M0[X, B0]
    type A = A0
    def TC = TC0
    def leibniz = refl
  }

  /**Unpack a value of type `M0[A0, B0]` into types `[b]M0[A0, b]` and `B`, given an instance of `TC` */
  implicit def unapplyMAB2[TC[_[_]], M0[_, _], A0, B0](implicit TC0: TC[({type λ[α] = M0[A0, α]})#λ]): Unapply[TC, M0[A0, B0]] {
    type M[X] = M0[A0, X]
    type A = B0
  } = new Unapply[TC, M0[A0, B0]] {
    type M[X] = M0[A0, X]
    type A = B0
    def TC = TC0
    def leibniz = refl
  }
TAG标签:
版权声明:本文由必威发布于必威-编程,转载请注明出处:函数式编程模式的运行方式以递归算法为主,我