箭头函数表达式

前一段时间,我观看了FP的讨论。 演讲的结尾很像斯卡拉语,不管语言如何,开始时都会很有趣。

在演讲中,演讲者尝试使用功能性方法迁移标准的类似命令式的应用程序。 我想检查一下是否可以在Kotlin中进行相同的操作。

这是第1 后在从命令式函数式编程的重点series.Other职位包括:

  1. 从命令式编程到使用Arrow的函数式编程 (本文)
  2. 从命令式编程到函数式编程,一种方法
  3. 从命令式编程到函数式编程:分组问题(以及解决方法)
  4. 从命令式编程到函数式编程:Dijkstra算法

原始代码

用Kotlin翻译后,原始代码如下所示:

privatevalrandom=SecureRandom()

funmain(args:Array<String>){
  println("What is your name?")
  valname=readLine()
  println("Hello, $name, welcome to the game!")
  varexec=true
  while(exec){
    valnumber=random.nextInt(5)+1
    println("Dear $name, please guess a number from 1 to 5:")
    valguess=readLine()?.toInt()
    if(guess==number)println("You guessed right, $name!")
    elseprintln("You guessed wrong, $name! The number was $number")
    println("Do you want to continue, $name?")
    when(readLine()){
      "y"->exec=true
      "n"->exec=false
    }
  }
}

部分功能

函数是函数编程的基础。

其中, 部分功能是个问题。 这些是某些特定值无法执行的功能。 例如, /是一个局部函数,因为没有数字可以被0除。在上面的代码段中,有2个局部函数。

在Java中,使用无效值调用部分函数会导致异常。 可以在调用堆栈中的任何位置捕获和管理此异常。 不幸的是,这对于FP无法实现。

让我们检查一下如何以FP友好的方式处理该问题。

String上调用toInt()

无效值是那些不是数字的值。

FP方法是将调用包装在Try对象中。 创建和使用很简单:

funparseInt(input:String?)=Try{input?.toInt()}

valguess:Try<Int?>=parseInt(readLine())
when(guess){
  isFailure->println("You did not enter a number!")
  isSuccess<Int?>->{
    if(guess.value==number)println("You guessed right, $name!")
    elseprintln("You guessed wrong, $name! The number was $number")
  }
}

实际上,这仍然是必须进行的编程。 Try通过fold()方法提供了真正的FP功能。 它需要两个lambda参数,第一个在失败的情况下执行,第二个在成功的情况下执行:

(readLine()asString) (1)
  .safeToInt() (2)
  .fold(
    {println("You did not enter a number!")}, (3)
    { (4)
      if(it==number)println("You guessed right, $name!")
      elseprintln("You guessed wrong, $name! The number was $number")
    }
  )

privatefunString.safeToInt()=Try{this.toInt()}
  1. 需要强制转换为不可为null的String ,因为在这种情况下不能为null
  2. parseInt()可以更好地作为扩展函数来处理
  3. 如果无法将String解析为数字,则将被调用
  4. 如果可以的话会被调用
决定是否继续

该代码段仅处理yn ,而不处理其他值。 我们只需要第三个分支来处理无效的值。

让我们添加它:

varcont=true
while(cont){
  cont=false
  println("Do you want to continue, $name?")
  when(readLine()?.toLowerCase()){
    "y"->exec=true
    "n"->exec=false
    else->cont=true (1)
  }
}
  1. 管理无关的输入

IO简介

IO模式是FP中的一种普遍模式。 它涉及在IO实例中包装值,异常和lambda,并传递它们。

Arrow是Kotlin的FP库。 它提供FP抽象,例如IO

下图是Arrow中IO类的(不完整)概述:

io类图

使用IO ,每个系统调用都可以包装在带有IO实例的纯函数中:

privatefunputStrLn(line:String):IO<Unit>=IO{println(line)}
privatefungetStrLn():IO<String?>=IO{readLine()}

从循环到递归

FP避开状态:纯函数的返回值应仅取决于其输入参数-而不是状态。

循环(无论是for还是while )都使用状态来退出循环。 因此,循环对FP不友好。 在FP中,循环应替换为递归调用。

例如,continue循环可以替换为:

privatefuncheckContinue(name:String?):Boolean{
  println("Do you want to continue, $name?")
  (readLine()asString).transform{it.toLowerCase()}.transform{
    when(it){
      "y"->true
      "n"->false
      else->checkContinue(name)
    }
  }
}

privatefun<T>String.transform(f:(String)->T)=f(this) (1)
  1. 默认的String.map()方法遍历String的字符

让我们在IO中包装以上代码段:

privatefuncheckContinue(name:String?):IO<Boolean>=IO.monad().binding{ (1)
  putStrLn("Do you want to continue, $name?").bind()
  (getStrLn()).map{it?.toLowerCase()}.map{
    when(it){
      "y"->true.liftIO() (2)
      "n"->false.liftIO()
      else->checkContinue(name) (3)
    }
  }
  .flatten() (4)
  .bind() (5)
}
.fix() (6)
  1. IO.monad().binding方法为IO提供了上下文
  2. 将简单的标量值转换为IO
  3. 由于绑定上下文不可用,因此无法在此处bind()
  4. 将IO <IO <?>>展平为简单的IO <?>
  5. 将IO绑定到其包装值
  6. 将类型从IOOf<A>IO<A>

触发包装器

IO是计算的包装器,但是直到调用终止方法之前,实际上没有任何计算发生。 它们是几种可用的方法:

  • unsafeRunSync()
  • unsafeRunAsync()
  • runAsync()
  • 等等

对于我们的示例,在我们的案例中, unsafeRunSync()绰绰有余:

funmain(args:Array<String>){
  println("What is your name?")
  valname=readLine()
  println("Hello, $name, welcome to the game!")
  gameLoop(name).unsafeRunSync() (1)
}
  1. 触发计算

可以像这样将“推到最大”:

funmain(args:Array<String>){
  mainIO(args).unsafeRunSync()
}

privatefunmainIO(args:Array<String>):IO<Unit>=IO.monad().binding{
  putStrLn("What is your name?").bind()
  valname=getStrLn().bind()
  putStrLn("Hello, $name, welcome to the game!").bind()
  gameLoop(name).bind()
}.fix()

更多挑剔

IO提供了map()方法。 在读取用户输入的值之后,可以使用它来转换返回的值, 例如

privatefuncheckContinue(name:String?):IO<Boolean>=IO.monad().binding{
  putStrLn("Do you want to continue, $name?").bind()
  (getStrLn()).map{it.toLowerCase()}.map{ (1)
    when(it){
      "y"->true.liftIO()
      "n"->false.liftIO()
      else->checkContinue(name)
    }
  }.flatten()
   .bind()
}.fix()

privatefungameLoop(name:String?):IO<Unit>=IO.monad().binding{
  putStrLn("Dear $name, please guess a number from 1 to 5:").bind()
  getStrLn().safeToInt().fold(
      {putStrLn("You did not enter a number!").bind()},
      {
        valnumber=nextInt(5).map{it+1}.bind()
        if(it.bind()==number)println("You guessed right, $name!")
        elseputStrLn("You guessed wrong, $name! The number was $number").bind()
      }
  )
  checkContinue(name).map{
    (if(it)gameLoop(name)
    elseUnit.liftIO())
  }.flatten()
   .bind()
}.fix()

privatefunnextInt(upper:Int):IO<Int>=IO{random.nextInt(upper)}
privatefunIO<String>.safeToInt()=Try{map{it.toInt()}}
  1. 不再需要transform()

结论

Kotlin在Arrow的帮助下使从传统的命令式代码过渡到面向功能的代码成为可能。 它是否相关取决于一个人的上下文。 在所有情况下,这都是袖手旁观的另一把戏。

这篇文章的完整源代码可以在Github上找到。

翻译自: https://blog.frankel.ch/imperative-functional-programming/1/

箭头函数表达式

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐