--- 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 ```