---
title: Astro課程 0710 - Ruby (Day2)
tags: astro, ruby
---
# 0710 Ruby (Day2)
# 練習
印出偶數且大於3的總和
方法一:用`do end`迴圈
```
list = [1, 4, 2, 4, 6, 7, 8, 2, 3, 2, 1, 6]
sum = 0
list.each do |item|
(item.even? && item > 3) ? (sum+= item) : sum
p sum
```
方法二:用`.select`方法
```
list = [1, 4, 2, 4, 6, 7, 8, 2, 3, 2, 1, 6]
p list.select { |x| x.even? && x > 3 }.sum
```
javascript也有跟select相似的方法
```javascript
list.filter ( (x) => x > 3 && x % 2 == 0 )
```
`( (x) => x > 3 && x % 2 == 0 )`
中間這一段為 callback function
## Callback
"做完之後再打給我"
回呼函式(callback function)是指能藉由參數(argument)通往另一個函式的函式。[Ref](https://developer.mozilla.org/zh-TW/docs/Glossary/Callback_function)
# Block
Block是一段不會被主動執行的程式碼
在Ruby/ Rails裡大量使用Block
這裡的`Rails`是`常數`
```ruby
Rails.application.routes.draw do
resources :restaurants
root 'restaurants#index'
get 'pages/about'
get "/contact_us", to "pages#contact"
end
```
Block有兩種寫法:`do... end`和 `{}`
## Block無法單獨存活
以下會造成語法錯誤
```ruby
{
puts "我是大括號型的Block"
#syntax error, unexpected '}', expecting end-of-input
}
```
```ruby
do
puts "我是do end型的Block"
end
#block.rb:21: syntax error, unexpected keyword_do_block, expecting end-of-input
```
## Block的執行方式
以下block, 不會被執行
- 因為block像寄生蟲一樣,只能依附在方法後面
- block會不會執行,要看宿主的臉色,會不會想讓block執行
```ruby
def say_hello
puts "Hi, "
end
say_hello {
puts "here! "
}
puts "there!"
```
Outputs
```bash
Hi,
there!
```
## 使用yield執行
```ruby
def say_hello
puts "Hi, "
yield
puts "大家好"
end
say_hello {
puts "here! "
}
puts "there!"
```
Output:
```ruby
Hi,
here!
大家好
there!
```
## 轉讓的時候,可以攜帶參數
```ruby
def say_hello
yield 3
end
#say_hello
say_hello { |n|
puts "Hi! "*n
}
```
Outputs
```
Hi! Hi! Hi!
```
如果yield後面沒寫參數=> 等於參數是`nil`
```ruby
def say_hello
yield nil
end
say_hello do
p "n"
p "m"
end
```
output
```
"n"
"m"
```
```ruby
def say_hello
yield 3, 4
end
say_hello do |n , m, l|
p n
p m
p l
end
```
output
```
3
4
nil
```
## 讓出控制權來有什麼好處?
把資料做過濾及篩選
Eg.
```
list = [* 1..100]
p list.select { |x| x > 50 }
```
## Block完成的時候也可以帶東西回來
```
def test_two
if yield(3)
puts "yes, it is 2"
else
puts "no, it is not 2"
end
end
test_two {|n|
puts "aaa"
n == 2
}
```
output
block回傳最後一行, 3 == 2 為`false`
```
no, it is not 2
```
## 沒有block但是使用yield就會出錯
`LocalJumpError`
```
def hello
yield
end
hello
```
output
```
Traceback (most recent call last):
1: from yield.rb:44:in `<main>'
yield.rb:42:in `hello': no block given (yield) (LocalJumpError)
```
實體變數和全域變數有預設值 `nil`
(區域變數沒有)
```
2.5.2 :002 > $123
=> nil
2.5.2 :003 > @aaa
=> nil
2.5.2 :006 > $ruby.is_a?(Object)
=> true
2.5.2 :007 > $php.is_a?(Object)
=> true
2.5.2 :008 > $ruby.is_a?(Object) {|oriented| language }
=> true
```
區域變數沒有預設值
```
2.5.2 :011 > y
出錯
Traceback (most recent call last):
2: from /Users/tingtinghsu/.rvm/rubies/ruby-2.5.2/bin/irb:11:in `<main>'
1: from (irb):13
NameError (undefined local variable or method `y' for main:Object)
2.5.2 :011 > y = y
=> nil
2.5.2 :012 > y
=> nil
```
## 變數提升 Variable Hoisting
面試常考! javascript有變數提升的現象
在執行任何程式碼前,JavaScript 會把函式宣告放進記憶體裡面,這樣做的優點是:可以在程式碼宣告該函式之前使用它。
[Ref](https://developer.mozilla.org/zh-TW/docs/Glossary/Hoisting)
```
console.log (x);
var x = 1;
=> 1
# creation phase
=> x = undefined
# execution phase
# => undefined
x = 1
```
但是在ruby會出錯
Javascript的`setTimeOut 3000` 和`Event Loop`也常考
# 練習:刻出自己的my_select方法
```
def my_select(list)
//實作內容
end
my_select([1, 2, 3, 4, 5]) { |i| i.odd? }
```
Eg.
```
def my_select(list)
result = []
list.each do |x|
if yield (x)
result << x
end
end
return result
end
p my_select([1, 2, 3, 4, 5]) { |i| i.odd? }
```
# 練習:刻出自己的my_map方法
```
# p [1, 2, 3, 4, 5].map { |x| x * 2 }
def my_map(list)
# 實作內容
end
p my_map([1, 2, 3, 4, 5]) { |x| x * 2 }
```
Eg.
```
def my_map(list)
# 實作內容
result = []
list.each do |element|
result << yield(element)
end
return result
end
p my_map([1, 2, 3, 4, 5]) { |x| x * 2 }
```
## `.select` & `.reject` 方法
```
list = [1, 2, 3, 4, 5]
p list.select { |x| x.odd? } # [1, 3, 5]
p list.reject { |x| x.odd? } # [2, 4]
```
# Block兩種寫法的不同
`大括號`的內容會先執行
`do end` 較弱
## 重要:javascript執行的先後順序
javascript
```
1 < 10 < 100
true
100 > 10 > 1
false
100 > 10
true
10 > 1
true
true > 1
false
1 + "2" + 3
"123"
1 + 2 + "3"
"33"
```
# Proc: 物件化的block
一般的方法只能被呼叫
```
def add_two()
2 + n
end
add_two(3)
```
Proc物件可以被當作參數丟來丟去
```
add_two = Proc.new { |x| x*2 }
add_two.call(3)
```
練習
```
list = [1, 2, 3, 4, 5]
double = Proc.new { |x| x*2 }
p list.map { |element| double.call(element) }
# => [2, 4, 6, 8, 10]
```
練習: lambda當參數傳進去 (龍哥說把lambda寫成一個小元件)
```
list1 = [1, 2, 3, 4, 5]
list2 = ["a", "b", "c"]
double = -> (x) { x * 2 }
p list1.map(&double)
```
output
```
[2, 4, 6, 8, 10]
[2, 4, 6, 8, 10]
["aa", "bb", "cc"]
["aa", "bb", "cc"]
```
練習
```
p list1.select { |x| x.odd? }
p list1.select(&:odd?)
```
output
```
[1, 3, 5]
[1, 3, 5]
```
P.S. javascript 高階函數(higher order function)
一般的函數就可以被當作參數丟來丟去
但是ruby一般的方法沒有此特性,必須先寫成Proc
# lambda
Proc的另一種寫法
```ruby
add_two = Proc.new { |n| n + 2 }
add_two = lambda { |n| n + 2 }
add_two = -> (n) { n +2 }
```
```ruby
add_two = 2
其實是
add_two = -> (x) { x * 2 } # 匿名函數
add_two.call(100)
def add_two # 具名函數
end
```
## lambda 在 Rails的應用
Eg. 在rails定義一個叫做cheap的scope
```ruby
class Book < ApplicationRecord
scope :cheap, -> { where ("price <= 100") }
end
```
`-> { where ("price <= 100") }`
大括號包成lambda, 變成可以使用的物件
(一段可以執行的程式碼,但是是被動的,必須要有人呼叫它)
scope有兩個參數 `:cheap`, `-> { where ("price <= 100") }`
## High Order Function
ruby `scope(:cheap, -> {...})`
javascript `list.map(double)`
將函數當作物件傳給其他函數
# 物件導向程式語言
## 為什麼要使用OOP?
將記憶體的位置`擬人化`的過程
物件 = 狀態(名詞)+ 行為(動詞)
Eg. 人
狀態:膚色
行為:吃飯、走路
類別:烤盤
實體:雞蛋糕
## 類別命名:必須是常數(第一個字大寫)
```ruby
class Cat
def eat(food)
puts "#{food} 好吃!"
end
end
```
大寫一定是常數,但不一定是類別
* 實體:使用`類別`來產生
```ruby
kitty = Cat.new
kitty.eat("八方雲集")
nancy = Cat.new
nancy.eat("海南雞飯")
```
output
```
八方雲集 好吃!
海南雞飯 好吃!
```
# 繼承
- 與其說是繼承,不如說是分類
- 把`共同的特徵`放在`同一個分類`
```ruby
class Animal
def walk(place)
puts "走去 #{place}!"
end
def eat(food)
puts "#{food} 好吃!"
end
end
class Cat < Animal
end
class Dog < Animal
end
kitty = Cat.new
kitty.eat("八方雲集")
nancy = Cat.new
nancy.eat("海南雞飯")
kitty.walk("公園")
nancy.walk("車站")
```
output
```
八方雲集 好吃!
海南雞飯 好吃!
走去 公園!
走去 車站!
```
# 初始化 `initialize`
`initialize`是一個特別的method,一出生就會做的第一個動作
```ruby
class Cat
def initialize
puts "hello 你好"
end
end
kitty = Cat.new # => hello 你好
# 什麼都不用做,一出生就會打招呼!
```
# 實體方法
作用在實體上的方法
`say_hello`: 作用在實體上面
```ruby
class Cat
def say_hello
puts "hi, 你好"
end
end
kitty = Cat.new #kitty實體
kitty.say_hello #say_hello方法,作用在kitty實體上
```
# 類別方法
必須在實體方法前面加上`.self`
- 類別之內`self.all`
- 這樣才能順利呼叫類別方法`Cat.all`
```ruby
class Cat
def say_hello
puts "hi, 你好"
end
def self.all
puts "全部的貓!"
end
end
kitty = Cat.new
kitty.say_hello # => hi, 你好 (實體方法)
Cat.all # => 全部的貓! (類別方法)
```
# singleton method 單體(單例)方法
可以在任意物件上定義任何方法
```
def kitty.fjdlfjdslfd
puts "hi"
end
kitty = Cat.new
kitty.fjdlfjdslfd
```
`all`類別方法其實是從`單體方法`來的:
```
class Cat
def Cat.all
puts "hi"
end
end
# singleton method 單體(例)方法
Cat.all
```
如果以後要改成`Dog`?
直接改成`self`
```
class Cat
def self.all
puts "hi"
end
end
Cat.all
```
javascript裡的`this` 類似`self`
(你在哪裡?我在`這裡`)
那`這裡`到底是哪裡?
[請看Kuro的講解](https://kuro.tw/posts/2017/10/12/What-is-THIS-in-JavaScript-%E4%B8%8A/)
# 為什麼要有類別方法?
直接請類別幫忙做事情
```
kitty = Cat.new
kitty.xxx
Cat.xxx
```
# 開放類別 Open Class
又稱為monkey patching
```
# rails
3.days.ago # 可以用
# ruby
3.days.ago # 會出錯 X
```
來改一下ruby讓`3.days.ago`跑得動
```
class Integer
def days
end
def ago
end
end
puts 3.days.ago
Traceback (most recent call last):
hello.rb:9:in `<main>': undefined method `ago' for nil:NilClass (NoMethodError)
# => 會出錯,因為 days 沒有回傳值
```
讓days回傳自己
```
class Integer
def days
self
end
def ago
"#{self} days ago!!"
end
end
puts 3.days.ago
puts 5.days.ago
```
開放類別可能會在背後偷偷做某些事情(但是我們不知道)...
```
# open class
# monkey patching
class Integer
alias :old_plus :+
def +(n)
puts "hey hey hey"
old_plus(n)
end
end
puts 1 + 2
puts 3 + 2
```
# Ruby 的存取控制
## public
只要沒有特別說明,預設是`public`
通常習慣把public method集中放在上面,
private method集中放在下面
## private
private 不能有明確的訊息接收者(receiver)
* 呼叫方法的時候不能有`.`小數點 (puts就是一種private方法)
```ruby
class Cat
def say_hello #public
puts "hello!"
end
private #在private範圍內設定的方法,不能隨意取用
def gossip
end
end
kitty = Cat.new
kitty.say_hello #hello!
kitty.gossip # private method `gossip' called for #<Cat:0x00007fcd4692c730> (NoMethodError)
```
Smalltalk程式語言
`2 + 3`
對物件2傳送+訊息,並把3傳進來
Ruby傳訊息的方式受到Smalltalk的影響
在Ruby裡是對Kitty物件送了say_hello()訊息
* kitty: 接收者
* say_hello(): 訊息
來查查看private方法有哪些:`self.class.private_methods.sort`
發現`private`和`public`都是private方法
```
2.5.2 :001 > self.class.private_methods.sort
=> [:Array, :Complex, :DelegateClass, :Float, :Hash, :Integer, :Rational, :String, :URI, :__callee__, :__dir__, :__method__, :`, :abort, :at_exit, :binding, :block_given?, :caller, :caller_locations, :catch, :eval, :exec, :exit, :exit!, :extended, :fail, :fork, :format, :gem, :gem_original_require, :gets, :global_variables, :included, :inherited, :initialize, :initialize_clone, :initialize_copy, :initialize_dup, :irb_binding, :iterator?, :lambda, :load, :local_variables, :loop, :method_added, :method_missing, :method_removed, :method_undefined, :open, :p, :prepended, :print, :printf, :private, :proc, :protected, :public, :putc, :puts, :raise, :rand, :readline, :readlines, :remove_const, :require, :require_relative, :respond_to_missing?, :select, :set_trace_func, :singleton_method_added, :singleton_method_removed, :singleton_method_undefined, :sleep, :spawn, :sprintf, :srand, :syscall, :system, :test, :throw, :trace_var, :trap, :untrace_var, :using, :warn]
```
如果寫了一個private method,再查詢
```
private
def hello
end
p self.class.private_methods.sort
```
會多了`:hello`的private method
```
[... :gets, :global_variables, :hello, ]
```
## 其實private也不是這麼private
使用`.send(:gossip)`
但是這樣會破壞封裝的原則
```
class Cat
private
def gossip
puts "我跟你說,你不能跟別人說"
end
end
kitty = Cat.new
kitty.send(:gossip) # 成功!
```
可以一層一層的往裡面傳
```
kitty = Cat.new
kitty.send(:gossip) # 成功!
kitty.send(:send, :gossip)
kitty.send(:send, :send, :gossip)
kitty.send(:send, :send, :send, :send,
:send, :send, :send, :send, :send, :send,
:gossip)
```
```
我跟你說,你不能跟別人說
我跟你說,你不能跟別人說
我跟你說,你不能跟別人說
我跟你說,你不能跟別人說
```
## protect
如果是protect, `self.hello`, `hello`兩種寫法都可以
但是private會出錯
```
class Smalltalk
def h
self.hello
hello
end
protected
def hello
end
end
```
# Superclass
```
class Animal
end
class Cat < Animal
end
kitty = Cat.new
p kitty.class
p kitty.class.superclass
p kitty.class.superclass.superclass
p kitty.class.superclass.superclass.superclass
p kitty.class.superclass.superclass.superclass.superclass
p kitty.class.superclass.superclass.superclass.class
```
output
```
Cat
Animal
Object
BasicObject
nil # BasicOject是最上層,再上去就是nil了
Class
```