Day9. functional programming in Ruby - Block Part2

初来乍到Ruby世界的读者们,绝对想不到原来Ruby 也有 curry, bind 等用法。这些语法对於JS的使用者应该很熟悉,在今年的IT铁人赛就看到很多介绍bind, curry

closure

Closure是程序语言中很基本的概念,首先要先讲Procclosure 的关系

https://ithelp.ithome.com.tw/upload/images/20210908/20115854RM85WJeGEs.png

Ruby的Block则是用{}do ... end 来做领域的展开,领域里和领域外是完全不同的世界。外面的值没有办法干预到里面,而里头的数值和运算也不会影响到外界。这就是closure的概念,我们称为闭包。

没有术式就不会有领域,而以 Ruby 来说,一共有下列几种术式能够展开领域。

#====== Proc
proc {}
Proc.new {}

#====== Proc(Lambda)
lambda {}
-> {}

#====== map, each...
[1, 2, 3].each {}

我们也可以自己做术式,而我们使用的术式为yield

yield

block_given?yield 的好朋友,所以我们先介绍block_given?

def apple_machine
  block_given? && 'block_used' || 'block_unused'
end

apple_machine    #=> block_unused
apple_machine {} #=> block_used

由上面的例子我们可以知道, block_given? 可以侦测apple_machine是否使用blockblock_given? 可以让我们在有block或没有block的情况下做应对,接着我们开始讲yield

# 情境1
def apple_machine
  yield if block_given?
end

apple_machine { 1 }      #=> 1
apple_machine { 1 + 1 }  #=> 2
apple_machine { [1, 2, 3].sum }  #=> 6

# 情境2
def washing_machine
  yield if block_given?
  
  2
end

apple_machine { 1 }      #=> 2
apple_machine { 1 + 1 }  #=> 2
apple_machine { [1, 2, 3].sum }  #=> 2

yield字面上的意思为让路。上述的情境1、情境2,想要传达给读者的讯息为不管是简单的1、稍微复杂的1+1,或者更复杂的[1, 2, 3].sum。在进入block以後,程序码的会先被Block拿走主控权,结束block以後再回归原来的程序。

# 情境3
def itday9_method(num = 0)
  yield(num + 1) if block_given?
end

itday9_method                    #=> nil
itday9_method(2) { |n| n*100 }   #=> 300
[1, 2, 3].map { |n| itday9_method(n) { |p| p*100 } } #=> [200, 300, 400]

我们可以利用yield将方法里头的值传出去给外面的Block,给外面的Block计算以後再回传运算的结果回来。情境3的用法很重要

实际的状况下,我们传的不会只是num+1,可以是绿界回传的付款结果、货运单状态、POS错误讯息等等。

yield或许不是个好写法,因为使用太多的Block会造成维护上的不易读性,不过个人在专案上用的地方还蛮多的。使用在只有自己在维护的专案里面,写了Block就可以少想很多设计流程。

以下有几个例子,可以正大光明在Rails使用Block

  • view:如视窗画面、卡片、页签
  • layout :搭配content_for,如使用Action Mailer
  • 设计流程里面的decorator pattern,可以用yield实现 ➡️ Day32 提到摊提

Bind

Bind 其实是 React 使用 class component 比较常见的写法,但 bind 不是只能绑定 this,还可以绑任何物件。下面的例子为,javascriptbind 将里面的this 与外面的[1, 2, 3] 绑在一起。

function Point() {
  return [...this, 4, 5, 6]
}

point = Point.bind([1, 2, 3])

point()
// [1, 2, 3, 4, 5, 6]

bind是一种可以和外部世界联络的桥梁,而这个方法在Ruby 也有。

a = 123              # 外部变数
block = proc { a }   # Procedure
block.binding.local_variable_get(:a)  # 绑定外部的变数
block.call                            #=> 123

Curry

CurryJavascriptfunction的一种进阶用法,在JS中也是汉汉老师常用的写法。此外,我也常常在面试中看到使用Curry的题目

/* 公司後辈的面试题目 */
function adder(a) {
  /* function 可以当作变数,因此我们可以将 function 回传出去。 
     意即为: function return 出来的结果还是 function */
  return function(b) {
    return a + b
  }
}

/* 公司专案常见的写法,写法等同於上方 */
const adder = a => b => (a + b)

/* 范例1: a = 1, b = 2 */
adder(1)(2)                        // 3

/* 范例2: 用在map */
[1, 2, 3].map(e => adder(1)(e))    // [2, 3, 4]

范例1用了两个括号把值回传出来,范例2为使用map 这个 Higher Order Function,我们在使用Higher Order Function时,里面的adder必须也是function。利用curry的特性,做一个匿名函数 e => adder(1)(e)提供给map使用。

curry绝对不是炫技用,而是为了做到把function可以变成变数的特性而衍生出的技巧。以下为汉汉老师实际上使用的curry实例

/* 实际应用上使用的 curry (看不懂没关系) */
export const ajaxReload = (table) => () => table.api().ajax.reload();
export const multiAjaxReload = (tables) => () => tables.forEach(table => table.api().ajax.reload())
export const dataWithStatus = (selectedValue, type = null) => e => ({...e, [checkedOrSelected(type)]: is_in(e.value, selectedValue)})

Ruby 也有一样的方法,我们来看以下例子

divisible_by = ->(x,y) { (y % x).zero? }.curry

(1..10).select(&divisible_by.call(5))  #=> [5, 10]
(1..10).select(&divisible_by.call(2))  #=> [2, 4, 6, 8, 10]

我们分别传5, 2 给divisible_by 这个物件对,接着select会使用divisible_by.call作为筛选。至於 &符号,我们会在Day10解释

Day10 我们会介绍&,以及详细说明yield的运作原理!

参考资料


<<:  前端工程师也能开发全端网页:挑战 30 天用 React 加上 Firebase 打造社群网站|Day9 发表文章页面

>>:  [13th-铁人赛]Day 4:Modern CSS 超详细新手攻略 - Display

Day14 - 【概念篇】OAuth flows: Implicit (Legacy)

本系列文之後也会置於个人网站 +----------+ | Resource | | Owner ...

[Day20] 第二十章 - 修改登入画面 (使用bootstrap 4.6的范例)

前言 昨天我们套用了bootstrap4.6 今天来把登入画面也套上去 并且测试api吧 目标 新增...

理解网际网路协定(一):何谓 IP Address

前面提到了一些 Router 连网所需的设定,有 PPPoE、DHCP 以及 Static 这几种类...

30天零负担轻松学会制作APP介面及设计【DAY 17】

大家好,我是YIYI,今天我要来制作记帐和报表的页面。 记帐页面 和制作前面的页面一样,先将BACK...

[Day14] swift & kotlin 实作篇!(5) 基本版面配置

swift 接下来~就让我们还拉一下版面吧 设定背景 首先点选左侧 Main.storyboard ...