Table of Contents
- Agenda
- FBI Warning
- 我是…
- 我是…
- 我还是
- 啊?
- 我在 JavaScript 社区讲函数式的时候, 观众是这样的…
- Ruby 函数式 ?
- 什么是函数式
- 好处呢?
- 你可能不知道的Ruby
- lambda aka 匿名函数
- 万物皆对象, lambda 也不例外
- 如果给这个lambda一个引用,我们可以跟用method一样用
- 三等公民
- 一等 vs 三等
- 一等公民 Proc
- 给三等座升个舱
- 升舱的魔法
#to_proc
- 升舱实例2 - Symbol
- 来 🍬 Desugar
&
- 模式匹配 pattern matching
- destructure - 数组
- destructure - 哈希
- case when
- 值
- 类型
- lambda (aka guard)
- 正则
- 但其实只是个简单的语法糖
- Category Theory
- 一个简单 🌰
- 命令式放大象
- 监控
- 或者用更极端的抛异常方式
- 广告时间
- 让我们用一个简单的 Either Monad
- 来简化控制流
- 怎么做到的
- Either 魔法
- 一个更实际的 🌰
- 上图有几次 IO
- 控制流不关心失败和监控
- IO自挂东南枝
- 还可不可以在纯一些
- Free Monad aka Interpreter Pattern
- 有些像 Cons
- 还有…
- Q/A
- 性能
- 并发多线程 made easy
- 多谢
Agenda
- 什么是函数式编程
- Ruby 的一些函数式特性
- 使用 Monad 纯化/简化控制流
FBI Warning
用 Nokia 的同学请自己手动输入 git.io/fprb
我是…
我是…
我还是
啊?
所以是…一个会点 Scala 的 JavaScript 程序员来教 Rubyist 函数式编程?
我在 JavaScript 社区讲函数式的时候, 观众是这样的…
Ruby 函数式 ?
You might be surprised to see Ruby in the list of functional languages because they generally count as object oriented languages. – Martin Odersky
你可能奇怪我把Ruby也放到了函数式语言的列表, 这些语言通常会被归到面向对象语言. – Scala 之父
什么是函数式
- 一等函数 first class function / 入 lambda
- 纯 purity
- 引用透明性 referential transparency
- 无副作用 side effectless
- 不可变 immutability
- 持久化数据结构 persistent data structures
…当纯到一定程度可能就需要
- 范畴论 Catergory Theory
好处呢?
- 好组合 composible
- 好推理 easy to reason about
- 好测试 easy to test
- 好多线程 Multi-thread
- 好玩 fun
- 好
高逼格 high biggerelegant
你可能不知道的Ruby
lambda aka 匿名函数
[多选题] 请选出所有的 lambda
A: {}/do end # such as =[1,2,3].map{|x| x+1 }= B: plus1 = lambda {|x| x + 1 } C: plus1 = -> (x) { x + 1 } D: plus1 = Proc.new { |x| x + 1 }
万物皆对象, lambda 也不例外
lambda 也就是一个正常的对象
plus1 = ->(x) { x + 1 }
#<Proc:0x007fbaea988030@-:3 (lambda)>
如果给这个lambda一个引用,我们可以跟用method一样用
plus1 = ->(x) { x + 1 } plus1.call(3) plus1.(3) plus1[3]
4
三等公民
def plus1 x x + 1 end [1,2,3,4].map &plus1
`plus1': wrong number of arguments (0 for 1) (ArgumentError)
一等 vs 三等
一等公民 Proc
plus1 = ->(x) { x + 1 } [1,2,3,4].map &plus1
[2, 3, 4, 5]
给三等座升个舱
def plus1 x x + 1 end first_class_plus1 = method(:plus1) [1,2,3,4].map &first_class_plus1
[2, 3, 4, 5]
升舱的魔法 #to_proc
method(:plus1) # => #<Method: Object#plus1>
class Method def to_proc lambda{|*args| self.call(*args) } end end
升舱实例2 - Symbol
%w(ouyang jichao).map &:capitalize # === %w(ouyang jichao).map { |x| x.capitalize}
["Ouyang", "Jichao"]
来 🍬 Desugar &
%w(ouyang jichao).map &:capitalize.to_proc
%w(ouyang jichao).map &Proc.new(|x| x.send(:capitalize))
["Ouyang", "Jichao"]
模式匹配 pattern matching
destructure - 数组
first, *middle_and_last = ['Phillip', 'Jay', 'Fry'] "first: #{first}, middle_and_last: #{middle_and_last}"
"first: Phillip, middle_and_last: [\"Jay\", \"Fry\"]"
destructure - 哈希
方法的参数会自带 destructure 哈希的功能 aka keyword arguments
:
fry = {first: 'Phillip', middle: 'Jay', last: 'Fry'} def printFirstName first:, **rest p first, rest end printFirstName fry
["Phillip", {:middle=>"Jay", :last=>"Fry"}]
case when
ruby 中的 case 可以搞定这几种模式匹配
- 值/表达式
- 类型
- Proc
- 正则
值
这个很简单,应该都有用过
me = 'ouyang' case me when 'ouyang' "hehe #{me}" else 'hehe jichao' end
hehe ouyang
类型
class Me def initialize name @name = name end def heheda "呵呵哒 #{@name}" end end me = Me.new 'ouyang' case me when Me me.heheda else '呵呵哒了' end
lambda (aka guard)
require 'ostruct' me = OpenStruct.new(name: 'jichao', first_name: 'ouyang') case me when ->(who){who.name=='jichao'} "hehe #{me}" end
hehe #<OpenStruct name="jichao", first_name="ouyang">
正则
case 'jichao ouyang' when /ouyang/ "呵呵哒" end
"呵呵哒"
但其实只是个简单的语法糖
case when 并不是magic,其实只是 if else 的语法糖, 比如上面说的正则
if(/ouyang/ === 'jichao') "heheda" end
所以 magic 则是所有 when 的对象都实现了 ===
方法而已
- 值:
object.===
会代理到==
- 类型:
Module.===
会看是否是其 instance - 正则:
regex.===
如果匹配返回 true - 表达式:取决于表达式返回的值的
===
方法 - lambda:
proc.===
会运行 lambda 或者 proc
说了这么些奇技淫巧,
逼格还是不够高呀除了花式一些有什么用呢?
纯 pure
Category Theory
Monad - 自函子范畴上的含幺半群
一个简单 🌰
把大象放冰箱里需要几步
命令式放大象
opened_fridge = open_fridge if opened_fridge fridge_w_elephent = put_elephent_in opened_fridge if fridge_w_elephent closed_fridge = close_fridge if closed_fridge 'yay' else 'fail to close fridge' end else 'fail to put elephent in' end else 'fail to open fridge' end
监控
opened_fridge = open_fridge if opened_fridge Monitoring.logger.info('fridge opened') fridge_w_elephent = put_elephent_in opened_fridge if fridge_w_elephent Monitoring.logger.info('puted a elephent into fridge') closed_fridge = close_fridge if closed_fridge Monitoring.logger.info('fridge closed') 'yay' else Monitoring.logger.error('no able to close fridge') 'fail to close fridge' end else Monitoring.logger.error('elephent put failed') 'fail to put elephent in' end else Monitoring.logger.error('fail to open fridge') 'fail to open fridge' end
或者用更极端的抛异常方式
begin close(put_elephent_in open_fridge) rescue A=>e ... rescue B=>e ... rescue C=>e ... end
广告时间
😹 ➡️ 😼 ⬇️ ↘️ ⬇️ 🙀 ➡️ 😻
猫呢?
让我们用一个简单的 Either Monad
gem install data.either
require 'data.either' Right.new(1).flat_map do |x| if x < 1 Left.new('meh') else Right.new(x+1) end end # => #<Right 2>
来简化控制流
open_fridge.flat_map do |fridge| # <= 1 put_elephent_in fridge # <= 2 end.flat_map do |fridge| close fridge # <= 3 end
这样可以专心构造控制逻辑,而不需要关心上一步如果错误该怎么办
怎么做到的
Either 魔法
def flat_map case self when Right yield @v else self end end
一个更实际的 🌰
用 microservices 组合成新的 service
上图有几次 IO
- 总共4个IO, 每一步骤都可能出错
- 但程序猿不希望漏掉任何错误信息
- 但是又不能为了监控,影响了这个简单的工作流
控制流不关心失败和监控
do a <- fetchA b <- fetchB c <- put $ blah a ++ b
IO自挂东南枝
def fetch(endpoint, decoder) response = self.class.get(endpoint, format: :json) case response.code when 410 Left.new(Exceptions::DataFailure.new("Resource #{endpoint} was deleted")) when 404 Left.new(Exceptions::DataFailure.new("Resource #{endpoint} not exist")) when 200 Right.new decoder.from_json(response.body) else Left.new(Exceptions::RepositoryError.new("Fetching #{endpoint} with Error:\n#{endpoint}, response code: #{response.code}")) end end
failure_processed, success_processed = Either.partition Mapinator.run Monitoring.send_processed success_processed.length Monitoring.logger.info("Processed successful #{success_processed.length} listings: #{success_processed}") Monitoring.logger.error("Processed FAILURE #{failure_processed.length} with Exceptions:") unless failure_processed.empty? ...
还可不可以在纯一些
Free Monad aka Interpreter Pattern
有些像 Cons
还有…
- Coyoneda
- Free Monoid
- State
- EitherT
- MaybeT
- …
这些我都不会讲…
因为我不会讲…
希望不久之后可以…
gem install control.monad.free
Q/A
性能
你TM都选 Ruby 了还在乎性能?
并发多线程 made easy
require "celluloid/autostart" module Enumerable def pmap(&block) futures = map { |elem| Celluloid::Future.new(elem, &block) } futures.map(&:value) end end
多谢
- https://blog.oyanglul.us/functional-ruby.html
- https://github.com/jcouyang/cats.rb
- https://github.com/typelevel/cats
- http://hackage.haskell.org/package/base-4.8.1.0/docs/src/Data.Either.html
- https://wiki.haskell.org/Free_structure
- http://underscore.io/blog/posts/2015/04/23/deriving-the-free-monad.html
- http://underscore.io/blog/posts/2015/04/14/free-monads-are-simple.html
- https://www.coursera.org/learn/progfun2
- https://www.amazon.com/Well-Grounded-Rubyist-David-Black/dp/1933988657
- https://mitpress.mit.edu/sicp
/
函数式 Ruby 编程 - 欧阳继超