# Functional Ruby

## lambda

``````plus1 = ->(x) { x + 1 }
``````
```#<Proc:0x0000000001441e38@-:4 (lambda)>
```

``````plus1 = ->(x) { x + 1 }
plus1.(3)
``````
```4
```

``````def plus1 x
x + 1
end
[1,2,3,4].map &plus1
``````
````plus1': wrong number of arguments (0 for 1) (ArgumentError)
```

``````plus1 = ->(x) { x + 1 }
[1,2,3,4].map &plus1
``````
```[2, 3, 4, 5]
```

## 神奇的 `&`

``````[1,2,3,4].map {|x| x + 1}
``````
```[2, 3, 4, 5]
```

``````[1,2,3,4].map &Proc.new{|x| x + 1 }
``````
```[2, 3, 4, 5]
```

``````%w(ouyang jichao).map &:capitalize
``````
```["Ouyang", "Jichao"]
```

desuger 完其实就是

``````%w(ouyang jichao).map &Proc.new(|x| x.send(:capitalize))
``````

``````%w(ouyang jichao).map &:capitalize.to_proc
``````
```["Ouyang", "Jichao"]
```

## 为什么 lambda 是 proc

### return

``````%w(ouyang jichao).map { |x| return 'lulu' if x == 'jichao'; x}
``````
```"lulu"
```

``````%w(ouyang jichao).map &->(x){ return 'lulu' if x == 'jichao'; x}
``````
```["ouyang", "lulu"]
```

### 参数检查

``````def heheda who
"heheda #{who}"
end
heheda
``````
````heheda': wrong number of arguments (0 for 1) (ArgumentError)
```

``````heheda = Proc.new{|who| p "heheda #{who}"}
heheda.()
``````
```"heheda "
```

Proc 完全不会理会参数，如果binding能找到，就用了，如果没有，也继续运行。

lambda，则更像一个method

``````heheda = lambda {|who| p "heheda #{who}"}
heheda.()
``````
````block in main': wrong number of arguments (0 for 1) (ArgumentError)
```

## 闭包

``````class HeHe
def initialize who
@who = who
end
def heheda
"heheda #{@who}"
end
end
``````

`HeHe` 对 who 进行了封装，如果需要访问 `who` 需要通过 `heheda` 方法。

``````who = 'jichao'
heheda = ->(){ "heheda #{who}" }
def hehedaToOuyang &heheda
who = 'ouyang'
heheda.()
end
hehedaToOuyang &heheda
``````
```"heheda jichao"
```

## pattern matching

ruby 支持简单的几种模式匹配

### destructure

``````first, *middle_and_last = ['Phillip', 'Jay', 'Fry']
p first, middle_and_last
``````
```["Phillip", ["Jay", "Fry"]]
```

destructuring 一个数组如此简单，但是hash就不这么容易，好在，方法的参数会自带 destructure的功能：

``````  fry = {first: 'Phillip', middle: 'Jay', last: 'Fry'}
def printFirstName first:, **rest
p first, rest
end
printFirstName fry
``````
```["Phillip", {:middle=>"Jay", :last=>"Fry"}]
```

``````1: fry = ['Phillip', 'Jay', 'Fry']
2: def printFirstName first, *rest
3: p first, rest
4: end
5: printFirstName *fry
``````
```["Phillip", ["Jay", "Fry"]]
```

### case when

#### 值

``````me = 'ouyang'
case me
when 'ouyang'
"hehe #{me}"
else 'hehe jichao'
end
``````
```"hehe ouyang"
```

#### 类型

``````class Me
def initialize name
@name = name
end

def heheda
"heheda #{@name}"
end
end

me = Me.new 'ouyang'

case me
when Me
me.heheda
else
'hehedale'
end
``````
```"heheda ouyang"
```

#### 表达式

`if else` 一样用

``````require 'ostruct'
me = OpenStruct.new(name: 'jichao', first_name: 'ouyang')
case
when me.name == 'jichao'
"hehe #{me}"
else 'gewuen'
end
``````
```"hehe #<OpenStruct name=\"jichao\", first_name=\"ouyang\">"
```

#### 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/
"heheda"
end
``````
```"heheda"
```

#### 其实只是个简单的语法糖

case when 并不是magic，其实只是 if else 的语法糖, 比如上面说的正则

``````if(/ouyang/ === 'jichao')
"heheda"
end
``````
```nil
```

• 值： `object.===` 会代理到 `==`
• 类型： `Module.===` 会看是否是其 instance
• 正则： `regex.===` 如果匹配返回 true
• 表达式：取决于表达式返回的值的 `===` 方法
• lambda： `proc.===` 会运行 lambda 或者 proc

## 一个简单的例子

• feed 失败了多少
• feeder 跑了没

#### Functor

``````module Either
def initialize v
@v = v
end

def map
case self
when Right
Right.new(yield @v)
else
self
end
end
alias :fmap :map
``````

``````def bind
case self
when Right
yield @v
else
self
end
end

alias :chain :bind
alias :flat_map :bind
``````

#### 一个好看的 inspect

``````  def inspect
case self
when Left
"#<Left value=#{@v}>"
else
"#<Right value=#{@v}>"
end
end
end
``````

#### 联合类型 Left | Right

``````class Left
include Either
def initialize v=nil
@v=v
end

def == other
case other
when Left
other.left_map { |v| return v == @v }
else
false
end
end
end

class Right
include Either
def == other
case other
when Right
other.map { |v| return v == @v }
else
false
end
end
end
``````

### 用 Either 做控制流

``````1: def run
2:   list_of_error_or_detail =
3:     listof_error_or_id.map do |error_or_id| # <-
4:     error_or_id.flat_map do |id| # <-
5:       error_or_detail_of(id) # <-
6:     end
7:   end
8:   list_of_error_or_detail.map { |error_or_detail| error_or_saved error_or_detail} # <-
9: end
``````
1. `listof_error_or_id` 是一个 IO, 去某个地方拿一串 id, 或者返回一串错误, 所以类型是 `[Either error id]`
2. 所以 `error_or_id` 的类型是 `Either error id`, `flat_map` 可以把 `id` 取出来, 如果有的话
3. 取出来的 `id` 交给 `error_or_detail_of`, 该函数也是 IO, 复杂获得对应 id 的 详细信息, 是IO就有可能会有错误, 所以返回值类型也是 `Either error detail`
4. 这时, 如果是用 `fmap` 转换完成后会变成一个 `Either error (Either error detail)`. 但显然我们不需要嵌套这么多层, `flat` 一些会变成 `Either error detail`
5. 后面的 save 函数也是类似的 IO 操作, 返回 `Either error saved`

``````failures, success = run.partition {|lr| !lr.is_a? Right}
error_msg = failures.map do |failure|
failure.left_map &:message
end.join "\n"
logger.error "processing failure #{failues.length}:\n#{error_msg}" unless error_msg.blank?
logger.info "processing success #{success.length}: #{success}"
``````

## actor model 多线程

### pmap

``````require "celluloid/autostart"
module Enumerable
def pmap(&block)
futures = map { |elem| Celluloid::Future.new(elem, &block) }
futures.map(&:value)
end
end
``````