从命令式编程到函数式编程,一种方法

上周 ,在Arrow库的帮助下,我们将Kotlin中的Scala应用程序从命令式移植到功能移植。 这很容易:为我们布置了示例。 这个星期,我想去买一些更基本的东西。

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

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

HackerRank上 ,存在以下问题:

考虑一个大小为4的楼梯:

   #
  ##
 ###
####

观察其底边和高度都等于n ,并使用#符号和空格绘制图像。 最后一行之前没有空格。

编写一个程序,打印大小为n的阶梯。

一个简单的解决方案命令式的问题如下:

funstaircase(n:Int):Unit{
  for(i:Intin0untiln){
    for(j:Intin0untiln){
      if(i+j<n-1)print(" ")
      elseprint("#")
      if(j==n-1)println()
    }
  }
}

该解决方案的主要问题是其可测试性。 从理论上讲,可以通过更改System.out写入的位置来对其进行测试,但这很麻烦(至少可以这样说)。 另外,如果多个测试进行相同的更改,则它们不能并行运行。 如FP所述,副作用是有害的。 而写出标准是一个巨大的副作用。

将副作用推到边缘

staircase()函数返回一个String ,将书写部分移到代码的边缘,然后输出结果:

funstaircase(n:Int):String{
  valbuilder=StringBuilder()
  for(i:Intin0untiln){
    for(j:Intin0untiln){
      if(i+j<n-1)builder.append(" ")
      elsebuilder.append("#")
      if(j==n-1)builder.append(System.lineSeparator())
    }
  }
  returnbuilder.toString()
}

这是解决可测试性问题的第一个(也是IMHO的主要步骤)。

删除循环

为了进一步走到FP路径,需要将循环迁移到递归性。

让我们首先迁移内部循环:

funstaircase(n:Int):String{
  valbuilder=StringBuilder()
  for(i:Intin0untiln){
      row(i,0,n,builder)
  }
  returnbuilder.toString()
}

privatefunrow(i:Int,j:Int,n:Int,builder:StringBuilder){
  builder.append(if(i+j<n-1)" "else"#")
  if(j<n-1)row(i,j+1,n,builder)
  elsebuilder.append(System.lineSeparator())
}

现在,到外循环:

funstaircase(n:Int):String{
  valbuilder=StringBuilder()
  column(0,n,builder)
  returnbuilder.toString()
}

privatefuncolumn(i:Int,n:Int,builder:StringBuilder){
  if(i<n){
    row(i,0,n,builder)
    column(i+1,n,builder)
  }
}

privatefunrow(i:Int,j:Int,n:Int,builder:StringBuilder){
  builder.append(if(i+j<n-1)" "else"#")
  if(j<n-1)row(i,j+1,n,builder)
  elsebuilder.append(System.lineSeparator())
}

优化一点

在Kotlin中, tailrec修饰符允许通过编写命令式字节码来优化递归函数的生成字节码。 唯一的要求是,递归调用必须是函数中的最后一个调用 。 让我们在row()函数中反转if / else条件:

privatetailrecfunrow(i:Int,j:Int,n:Int,builder:StringBuilder){
  builder.append(if(i+j<n-1)" "else"#")
  if(j>=n-1)builder.append(System.lineSeparator())
             elserow(i,j+1,n,builder)
}

同样, tailrec可以按tailrec应用于column()函数。

移除状态

最后一个问题是val builder = StringBuilder() 。 状态与FP不兼容。 此外, column()row()函数不会返回,也不是函数。

让我们将现有代码迁移到纯函数。 既然有点复杂,我们将分多个步骤进行。

第一步是显式返回StringBuilder ,同时保留累加器( builder参数):

funstaircase(n:Int):String{
  returncolumn(0,n,StringBuilder()).toString()
}

privatefuncolumn(i:Int,n:Int,builder:StringBuilder):StringBuilder{
  if(i<n){
    row(i,0,n,builder)
    returncolumn(i+1,n,builder)
  }
  returnbuilder
}

接下来的步骤是将类型更改为一个不变的一个- String代替StringBuilder

funstaircase(n:Int):String{
  returncolumn(0,n,"")
}

privatefuncolumn(i:Int,n:Int,string:String):String{
  if(i<n){
    valbuilder=StringBuilder(string)
    row(i,0,n,builder)
    returncolumn(i+1,n,builder.toString())
  }
  returnstring
}

现在,让我们对row()函数使用相同的技术。 首先,介绍一个返回类型:

privatefuncolumn(i:Int,n:Int,string:String):String{
  if(i<n){
    valbuilder=StringBuilder(string)
    valrow=row(i,0,n,builder).toString()
    returncolumn(i+1,n,row)
  }
  returnstring
}

privatetailrecfunrow(i:Int,j:Int,n:Int,builder:StringBuilder):StringBuilder{
  builder.append(if(i+j<n-1)" "else"#")
  returnif(j>=n-1)builder.append(System.lineSeparator())
                    elserow(i,j+1,n,builder)
}

最后,让我们如上所述更改数据类型:

privatefuncolumn(i:Int,n:Int,string:String):String{
  if(i<n){
    valrow=row(i,0,n,string)
    returncolumn(i+1,n,row)
  }
  returnstring
}

privatetailrecfunrow(i:Int,j:Int,n:Int,string:String):String{
  valline=if(i+j<n-1)"$string "else"$string#"
  returnif(j>=n-1)line+System.lineSeparator()
                    elserow(i,j+1,n,line)
}

挑剔

在上面的代码中,仍然有两个声明的变量rowline 。 如果要走整个九码,则需要将其拆除。 为此,很容易引入另一个(纯)函数:

privatetailrecfuncolumn(i:Int,n:Int,string:String):String=
  if(i>=n)string
         elsecolumn(i+1,n,row(i,0,n,string))
}

privatetailrecfunrow(i:Int,j:Int,n:Int,string:String):String=
  if(j>=n-1)line(i,j,n,string)+System.lineSeparator()
             elserow(i,j+1,n,line(i,j,n,string))
}

privatefunline(i:Int,j:Int,n:Int,string:String)=
  if(i+j<n-1)"$string "
                else"$string#"

奖励:一些Clojure的爱

在我最近尝试学习Clojure时,这是等效的Clojure代码:

(defn line [i, j, n, string]
  (if (< (+ i j) (- n 1))
    (str string " ")
    (str string "#")))

(defn row [i, j, n, string]
  (if (>= j (- n 1))
    (str (line i j n string) (System/lineSeparator))
    (row i (+ j 1) n (line i j n string))))

(defn column [i, n, string]
  (if (>= i n)
    string
    (column (+ i 1) n (row i 0 n string))))

(defn staircase [n]
  (column 0 n ""))

结论

至于OOP,因此迁移到FP会使代码更难阅读和维护。 恕我直言,只要将console-write调用推到main函数之外就足够了。 当然,这取决于您(和您的团队)对FP的熟悉程度和经验。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值