箭头函数表达式_使用箭头从命令式编程到函数式编程
箭头函数表达式
前一段时间,我观看了FP的讨论。 演讲的结尾很像斯卡拉语,不管语言如何,开始时都会很有趣。
在演讲中,演讲者尝试使用功能性方法迁移标准的类似命令式的应用程序。 我想检查一下是否可以在Kotlin中进行相同的操作。
这是第1 周后在从命令式函数式编程的重点series.Other职位包括:
- 从命令式编程到使用Arrow的函数式编程 (本文)
- 从命令式编程到函数式编程,一种方法
- 从命令式编程到函数式编程:分组问题(以及解决方法)
- 从命令式编程到函数式编程: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()}
- 需要强制转换为不可为null的
String,因为在这种情况下不能为null - parseInt()可以更好地作为扩展函数来处理
- 如果无法将
String解析为数字,则将被调用 - 如果可以的话会被调用
-
决定是否继续
该代码段仅处理y和n ,而不处理其他值。 我们只需要第三个分支来处理无效的值。
让我们添加它:
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)
}
}
- 管理无关的输入
IO简介
IO模式是FP中的一种普遍模式。 它涉及在IO实例中包装值,异常和lambda,并传递它们。
Arrow是Kotlin的FP库。 它提供FP抽象,例如IO 。
下图是Arrow中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)
- 默认的
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)
IO.monad().binding方法为IO提供了上下文- 将简单的标量值转换为IO
- 由于绑定上下文不可用,因此无法在此处
bind() - 将IO <IO <?>>展平为简单的IO <?>
- 将IO绑定到其包装值
- 将类型从
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)
}
- 触发计算
可以像这样将“推到最大”:
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()}}
- 不再需要
transform()
结论
Kotlin在Arrow的帮助下使从传统的命令式代码过渡到面向功能的代码成为可能。 它是否相关取决于一个人的上下文。 在所有情况下,这都是袖手旁观的另一把戏。
翻译自: https://blog.frankel.ch/imperative-functional-programming/1/
箭头函数表达式
更多推荐

所有评论(0)