Day8. functional programming in Ruby - Block Part1

这篇文章会用不一样的观点来介绍Ruby and Rails,写过 Javascript 的读者们,相信这篇文章对你们会比较有感觉。

Ruby 为正统的OOP语言,但Ruby也 开了一扇functional programming的窗,让我们可以使用,而今天我就要为读者们打开这扇窗户。

Block

首先要介绍什麽是区块blockblock 为以 do ... end 或者以 {} 包裹住的区块,以下面的例子来说,{|_| _ + 1} 就是 block

[1, 2, 3].map {|_| _ + 1}

在 Ruby 什麽东西都是物件,不过也有例外,例如Block本身就不是物件。Block没有办法单独的存在,也没办法指定给某个变数。虽然是例外,但 block却是Ruby常用的技巧。不仅面试常考,很多底层也很常使用block

{|_| _ + 1}      #=> 不行这样表示
a = {|_| _ + 1}  #=> 当作变数也不行

#==== 取而代之,我们可以这样写
a = -> (v) { v + 1 }

Procedure

下面的写法为 Proc

a = -> (v) { v + 1 }

Block 没有办法单独存在,但我们可以使用 Proc 类别,把 Block 物件化。Proc全名称为 Proceduce,而Proc一共有两种,一种为lambda, 而另外一种为 Proc

#======= lambda
-> { 'foo' }
#=> #<Proc:0x00007f883d48dfd8@(irb):2 (lambda)>
lambda { 'foo' }
#=> #<Proc:0x00007f883d49f0f8@(irb):3 (lambda)>

#======= Proc
Proc.new { 'foo' }
#=> #<Proc:0x00007f883d4afc50@(irb):4>
lambda { 'foo' }    #=> Proc
-> { 'foo' }.class  #=> Proc

Proc.new { 'foo' }  #=> Proc

接着,讲讲如何执行procedure

adder = -> (x) {x+1}

# 4种 notation
adder.call(1)

adder.(1)
adder.=== 1
adder[1]

一般来说,除了call 这种 notation 以外,其他用法长相太奇异,一般来说都不建议使用,但如果想要用proc,又想要在code review通关的话,建议使用最後一种notation,因为可以被当作使用阵列蒙混过去。

def arb_mtd
  # 程序码逻辑(多行)
  adder = -> (x) {x+1}
  # 程序码逻辑(多行)
  adder[1]
  # 程序码逻辑(多行)
end

functional Programming

Ruby on Rails是很物件导向的语言,而汉汉老师曾经有一段时间因前端部门的人太少,我被转调到前端部门。听到大家讨论在前端专案实作什麽写法的时候,第一次听到functional programming这个名词。起初对於什麽是functional programming一头雾水,看到同事的程序码吓到,原来functional programming在专案上竟然能够呈现得那麽好看!

主要也是同事也好看。

总之开始看下列例子。以下的例子为将[1, 2, 3] 的阵列分别加1,回传新的值

[1, 2, 3].map {|_| _ + 1}

我们使用Javascript来诠释以上的写法

/* es6 map */
[1, 2, 3].map(e => e + 1)

/* map in lodash */
map([1, 2, 3], e => e + 1)

依样画葫芦,我们也将原本Ruby的写法进行改写。这里先不要理会&的写法,我们只是尽量将JS, Ruby写法达到一致。

[1, 2, 3].map(&->(x) { x + 1 })

Ruby, JS两个语言使用了匿名函式如下

e => e + 1        # anonymous in js
->(x) { x + 1 }   # anonymous in ruby

并且,这两个匿名函式皆为 Pure function,又为 First Class Citizens。今天突然讲了很多新名词,不过我会一一介绍给大家。

Pure function

我们先介绍Pure functionPure Function 有一个最重要的特性是当输入了相同的值,吐出的值都是相等的结果,也有人说这种特性是可快取的(cached)Vue2computed property 即为一种Pure function,使用Pure function 不会有side effect,意即不会改变任何结果!

First Class Citizens

什麽是First Class Citizens?First Class Citizens 为将function 视为参数,指派到其他function使用。以上例来说,->(x) { x + 1 }为第一等公民,它将自己提供给别人给使用。

Higher Order Function

使用其他function作为参数使用的function,或者回传结果为function都为 Higher Order Function。上述的map 使用了匿名函数,因此map高阶函数 higher order function。

我们举个简单的例子讲解 Higher Order FunctionFirst Class Citizens之间的关系

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

function showOk() {
  alert( "OK" );
}

function showCancel() {
  alert( "Canceled" );
}

// usage: functions showOk, showCancel are passed as arguments to ask
ask("Do you agree?", showOk, showCancel);

上述例子我们可以知道 showOk, showCancel都被ask拿来取用,因此上述例子的 Higher Order Functionask,而 showOk, showCancel 则为 First Class Citizens

以下列出 Javascript 常见的高阶函数,其中hello, squareFirst Class Citizens,而使用hello, squaresetTimeout, map则为 higher order function

// setTimeout
const hello = () => console.log("Hello!")
window.setTimeout((hello), 1000)

// map (用lodash的观点来看)
const square = (n) => (n * n);
_.map([4, 8], square);
// => [16, 64]

Rails不难发现Higher Order Function 的踪迹,以下举一些复杂的例子。

# gem: AASM 
#=== 定义订单完成动作
event(:complete, success: -> { update_status_time!(:done_at) }) do
  transitions from: [Status::WAITING, Status::PROCESSING], to: Status::DONE
end

首先我们看AASM的例子,它把success: -> { update_status_time!(:done_at) } 传进去event,因此event在这边为 higher order function,而-> { update_status_time!(:done_at) }First Class Citizens

# gem: descent_exposure
#===== 取代 controller instance variable 的好用工具
module Admin
  class ReturnOrdersController < ApplicationController
    expose :return_orders, -> { ReturnOrder.all }
    expose :return_order, build: -> (return_order_params, scope) { scope.build(return_order_params) },
                          scope: -> { ReturnOrder.all }
    expose :order, -> { return_order.order }
  end
end

上面的例子,毫无疑问的,exposehigher order function,而build: -> {...}, scope: -> {...} 则为被调用的First Class Citizens

结语

自从踏入了Functional Programming的领域之後,Block变成是我很喜欢的章节之一。今天介绍了 Ruby 和 Functional Programming 之间关联,我们也会继续在Day8-9 介绍Block

参考资料


<<:  [Day 03] 用 Gradle 安装 Exposed 框架

>>:  DAY3 圣杯布局 - 三个栏的C位争夺战(上)

[Day6] 词性标注(一)-前言

一. 前言 词性标注 Part Of Speech(後面皆简称POS),简单来说就是将文章、句子中,...

Android 手机 行动电话 小人图示 talkback 无障碍按钮 导览列 捷径 协助工具按钮开关 设定 隐藏 开启

Android 手机 行动电话 小人图示 talkback 无障碍按钮 导览列 捷径 协助工具按钮开...

新零售行销模式案例,全通路时代来临该如何布局

新零售行销模式案例,全通路时代来临该如何布局,一直以来都在担任辅导顾问为中小企业解决网路行销问题但都...

Day-22 树(Tree), 二元搜寻树(Binary Search Tree)

前言 对於大量的资料处理,使用串列的走访是一种十分没有效率的方法,其效率会根据串列的长度而不断线性成...

第六章 之三

上次提到了在wordpress建置时选择主题的方式,本次就来看一下有关Themes购买,一个是在wo...