设为首页 加入收藏

TOP

Scalaz(22)- 泛函编程思维: Coerce Monadic Thinking(二)
2017-10-10 12:13:25 】 浏览:8725
Tags:Scalaz 编程 思维 Coerce Monadic Thinking
gt; trigger: => scalaz.Id.Id[String]
trigger //> res0: scalaz.Id.Id[String] = SomeDevice.On }

这段代码前面用trait进行了功能需求描述,接着用Reader定义依赖,再接着通过Reader组合实现了依赖的层级式管理,直到形成最终的Reader组合:

object MockAppliance extends Appliance with DeviceFunctions with PowerFunctions

这些都没什么问题,也体现了函数式编程风格。问题就出在这个trigger函数定义里,我们来看看:

def trigger =
  if ((PowerService.isUSStandard("CHN")(MockAppliance)) && (SensorService.isCoffeePresent(MockAppliance))) OnOffService.on(MockAppliance) else OnOffService.off(MockAppliance) //> trigger: => scalaz.Id.Id[String]

首先感觉代码很乱;每句都有个MockAppliance很笨拙(clumsy),感觉不到任何优雅的风格,也看不出与常用的OOP编程有什么分别。

回忆下当时是怎么想的呢?trigger的要求是:如果电源是US标准并且壶里能检测到有咖啡,那么就可以启动加热器,否则关停。

已经完成了电源标准和咖啡壶内容检测即加热器开关的组件(combinators)。都是细化了的独立功能函数,这点符合了函数式编程的基本要求。

当时的思路是这样的:

1、获取当前电源制式,判断是否US标准 

2、获取咖啡壶检测数据,判断是否盛载咖啡

3、if 1 and 2 then OnoffService.on else OnOffService.off

但是为了获取1和2的Boolean结果就必须注入依赖:MockAppliance,所以在trigger函数定义里进行了依赖注入。现在看来这就是典型的OOP思想方式。

首先我们再次回想一下函数式编程的一些最基本要求:

1、纯代码(pure code):实现函数组合-这点在前面的功能函数组件编程中已经做到

2、无副作用(no-side-effect):尽量把副作用推到程序最外层,拖延到最后-trigger使用了依赖MockAppliance,产生了副作用

3、我经常提醒自己Monadic Programming就是F[A]:A是我们要运算的值,我们需要在一个壳子内(context)对A进行运算。

看看这个版本的trigger:因为直接获取了isUSStandard和isCoffeePresent的Boolean运算值所以需要立即注入依赖。首先的后果是trigger现在是有副作用的了。再者trigger和MockAppliance紧紧绑到了一起(tight coupling)- 如果我们再有个Reader组合,比如什么DeployAppliance的,那我们必须再搞另一个版本的trigger了。即使我们通过输入参数传入这个Reader组合依赖也会破坏了函数的可组合性(composibility),影响函数组件的重复利用。看来还是按照上面的要求把这个trigger重新编写:

  object MockAppliance extends Appliance with DeviceFunctions with PowerFunctions def trigger(cntry: String) = for { isUS <- PowerService.isUSStandard(cntry) hasCoffee <- SensorService.isCoffeePresent onoff <- if (isUS && hasCoffee) OnOffService.on else OnOffService.off } yield onoff       //> trigger: (cntry: String)scalaz.Kleisli[scalaz.Id.Id,Exercises.Exercises.rea //| derDI.Appliance,String]
  trigger("CHN")(MockAppliance)                   //> res0: scalaz.Id.Id[String] = SomeDevice.On
  trigger("HK")(MockAppliance)                    //> res1: scalaz.Id.Id[String] = SomeDevice.Off
 

现在这个版本的trigger是一段纯代码,并且是在for-comprehension内运算的,与依赖实现了松散耦合。假如这时再有另一个版本的依赖组合DeployAppliance,我们只需要改变trigger的注入依赖:

  trigger("CHN")(DeployAppliance)                   //> res0: scalaz.Id.Id[String] = CoffeeMachine.On
  trigger("HK")(DeployAppliance)                    //> res1: scalaz.Id.Id[String] = CoffeeMachine.Off

怎么样?这样看起来是不是简明高雅许多了?

噢,祝大家新年快乐!

 

 

 

首页 上一页 1 2 下一页 尾页 2/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Scalaz(21)-类型例证:Liskov .. 下一篇Scalaz(23)- 泛函数据结构: Z..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目