高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

在Ruby如何找到方法的定義?

剛好昨天有朋友問到,就順便筆記一下。

Ruby官方文件很多,但有時對入門的朋友來說,常會遇到「我想看看XX方法的詳細用法,我該找哪個類別或模組的文件?」的問題。簡單的說,「常翻文件」就是這個問題最好的答案啦,除此之外,你也可以使用”Object#method“來找。

例如我想要知道puts這個很常用的方法是定義在哪邊的:

1
print method(:puts)   # <Method: Object(Kernel)#puts>

它會回傳一個Method類別的實體,並且寫著Object(Kernel)#puts,表示這個puts方法是被定義在Object的(事實上是在Kernel,透過Mixin混到Object裡的)

回傳的這個Method類別實體其實也可以直接呼叫,例如:

1
2
my_puts = method(:puts)
my_puts.call("Hello Ruby")

另外在寫Rails的時候,有時候想要知道某個好用的方法或helper是放在哪邊,想看看原始碼是怎麼寫的,也可以透過這個方式(Ruby 1.9限定):

1
2
3
require 'active_support/all'
puts 10.day.method(:ago).source_location
# ["/Users/eddie/.rvm/gems/ruby-1.9.2-p180/gems/activesupport-3.0.9/lib/active_support/core_ext/numeric/time.rb", 63]

Method#source_location會回傳一個陣列,裡面放著檔名以及在第幾行。

參考資料:

Ruby API – Method類別

在Titanium Studio使用CoffeeScript來寫app

既然CoffeeScript寫出來的東西就是一般的JavaScript,之前就在猜想是不是也可以把CoffeeScript用在Titanium Studio裡,果然,有善心人士用Python寫了給Titanium Studio的外掛。

安裝

首先,你需要在你電腦裡安裝好CoffeeScript的相關工具,詳情可參考這篇

相關的工具安裝完成之後,接著請到 https://github.com/billdawson/ti_coffee_plugin 下載壓縮檔,解壓縮之後把plugin.py這個檔案放到Titanium Studio的plugin資料夾。以我的電腦來看是放在這個資料夾:

/Library/Application Support/Titanium/plugins/ti_coffee_plugin/1.0/plugin.py

如果資料夾不存在的話,就自己手動建吧,檔案放進去之後就算安裝完成了。

使用

在每個Titanium Studio的project底下,應該都可以找到一個”tiapp.xml“的檔案:

image

找到它之後,請在最下面加上這段:

<plugins>
    <plugin version="1.0">ti_coffee_plugin</plugin>
</plugins>

image

接下來,因為我們要建一個app.coffee來產生app.js,所以原來的app.js就可以先丟掉,然後在Resources資料夾裡建一個叫app.coffee的檔案:

image

接著就是打開app.coffee來寫code啦,如果大家懶得寫,可以直接剪貼這段直接貼上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Titanium.UI.backgroundColor = 'white'

win1 = Titanium.UI.createWindow(
  backgroundColor: 'white'
  title: 'CoffeeScript Demo'
)

label1 = Titanium.UI.createLabel(
  color: '#999'
  text: 'Hello CoffeeScript'
  font:
      fontSize:30
      fontFamily: 'Helvetica Neue'
  textAlign: 'center'
  width:'auto'
)

win1.add label1
win1.open()

打完收工,按下build按鈕,耐心等一下畫面..

image

同時你應該也會發現在Resources資料夾裡自動長出了app.js,這個就是神奇的plugin幫我們在build的時候,自動幫我們完成從編譯js檔的工作。

為什麼好好的JavaScript要改用CoffeeScript來寫?

大家先看看程式碼的畫面:

image

主要是CoffeeScript的語法簡單、清楚很多,例如像是物件可以用YAML的方式來寫。所以像label1裡的參數本來是一層包一層,可以改用更清楚的YAML式的寫法(不過這個多少有個人觀感,說不定有的人覺得原來用大括號的比較清楚)。

好寫是好寫,但也不是完全沒缺點,大家自己看畫面應該會發現到,例如原本Titanium Studio有syntax highlight改用CoffeeScript就沒了,當然code hint就別想了(不知道會不會有善心人士也把這個功能補完),不然就是先到有支援CoffeeScript的編輯器裡先寫好,再回來Titanium Studion裡進行編譯也是一招。

所以各位客倌就自己取捨囉,供大家參考。

CoffeeScript OOP篇

在JavaScript裡寫OOP有好幾種方式,例如你可以使用prototype的方式,也可以使用function或物件的方式來寫,不過其實不管哪一種,類別的程式碼寫起來、看起來都不夠直覺(當然也部份是因為我對JavaScript沒這麼熟)。還好在CoffeeScript寫類別是相當容易,而且程式碼是很清楚、容易閱讀的,來看個例子:

1
2
3
4
5
6
7
class Animal
  constructor: (name, age) ->
      this.name = name
      this.age = age

animal = new Animal("eddie", 18)
console.log animal

編譯結果:

1
2
3
4
5
6
7
8
9
10
var Animal, animal;
Animal = (function() {
  function Animal(name, age) {
      this.name = name;
      this.age = age;
  }
  return Animal;
})();
animal = new Animal("eddie", 18);
console.log(animal);

看到了嗎? 用CoffeeScript來定義類別真的簡單多了。特別注意一下constructor(建構子)的部份,這邊的:

1
this.name = name

因為在CoffeeScript裡@符號代表this,所以可以簡寫成:

1
@name = name

整個很有Ruby味道。又,因為建構子用的this.name = name這樣指定動作太常見,所以:

1
2
3
4
class Animal
  constructor: (name, age) ->
      this.name = name
      this.age = age

可以更簡化成:

1
2
class Animal
  constructor: (@name, @age) ->

方法

那如果要加其它的function到類別裡要怎麼做?

1
2
3
4
5
6
7
8
class Animal
  constructor: (@name, @age) ->

  say_hello: (something) ->
      console.log "Hello, #{something}"

animal = new Animal("eddie", 18)
animal.say_hello("CoffeeScript")

編譯結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
var Animal, animal;
Animal = (function() {
  function Animal(name, age) {
      this.name = name;
      this.age = age;
  }
  Animal.prototype.say_hello = function(something) {
      return console.log("Hello, " + something);
  };
  return Animal;
})();
animal = new Animal("eddie", 18);
animal.say_hello("CoffeeScript");

編譯出來的程式碼已經有點開始長了..

繼承

在CoffeeScript裡,繼承使用extends這個關鍵字:

1
2
3
4
5
6
7
8
9
10
11
12
class Animal
  constructor: (@name, @age) ->
  say_hello: (something) ->
      console.log "Hello, #{something}"

class Human extends Animal
  walk: ->
      console.log "I can walk with my foots!"

eddie = new Human("eddie", 18)
eddie.say_hello "CoffeeScript"
eddie.walk()

編譯結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var Animal, Human, eddie;
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
  for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
  function ctor() { this.constructor = child; }
  ctor.prototype = parent.prototype;
  child.prototype = new ctor;
  child.__super__ = parent.prototype;
  return child;
};
Animal = (function() {
  function Animal(name, age) {
      this.name = name;
      this.age = age;
  }
  Animal.prototype.say_hello = function(something) {
      return console.log("Hello, " + something);
  };
  return Animal;
})();

Human = (function() {
  __extends(Human, Animal);
  function Human() {
      Human.__super__.constructor.apply(this, arguments);
  }
  Human.prototype.walk = function() {
      return console.log("I can walk with my foots!");
  };
  return Human;
})();
eddie = new Human("eddie", 18);
eddie.say_hello("CoffeeScript");
eddie.walk();

看到編譯出來的JavaScript程式碼了嗎? 跟CoffeeScript的程式碼行數以及複雜度比起來,我想誰比較容易寫應該一目了然了。

在OOP的繼承體系裡,子類別會自動繼承自父類別的方法,如果兒子覺得老爸的方法不好用,兒子也可以自己定義一個來用,以上面的例子來說:

1
2
3
4
class Human extends Animal
  say_hello: (something) ->
      console.log "Hi, I am #{@name}"
      super something

你可以在子類別定義同名的function,以來取代或補充父類別的方法,如果在override的方法裡要存取父類,則是使用關鍵字super

有興趣的話,大家可以自己試著編譯看看這時候的程式碼是不是已經有點看不太懂了,但CoffeeScript的部份應該看起來還算滿清楚的。

CoffeeScript 基本篇(二)

邏輯判斷

其實就是if..else那類的事,沒什麼特別的。不過在CoffeeScript裡有幾個比較有趣的點:

  • 不需要特別寫then:除非是寫成一行,不然不需要特別加上”then”
  • 有unless可以用:unless就是not if,if就是not unless,也許你會覺得有if not了何必需要unless呢? 其實每個人對語意的理解都有些差別,有的人覺得”除非怎樣怎樣”比較通順,有的人覺得”如果不要怎樣怎樣..”比較通順,其實指的都是同一件事,隨個人喜好。
  • if後面的判斷句如果在不影響語意的情況下,可以不需要小括號,例如:
1
2
if age > 18
  console.log "Yes, You are Adult"
  • 如果if / unless接的內容只有一行的話,if / unless 可以像一般英文敘述句一樣,放到最後面,例如:
1
console.log "Yes, You are Adult" if age > 18

為什麼要放到後面? 有什麼好處? 通常把if丟後面去,除了縮短一行的行數、程式碼讀起來更像一般口語之外,以上面這個例子來說,會有特別”強調”或”希望它執行”前面那個動詞的意味(個人觀感)。 * 在比大小的時候,跟Python借來的大於、小於用法,讓語法更清楚,例如:

1
2
body_weight = 70
normal_body_weight = 65 < body_weight < 80

有if..else,當多個if..else判斷串在一起的時候,CoffeeScript也有switch可以用:

1
2
3
4
5
6
switch username
  when "eddie" then console.log "You are  the King"
  when "nayumi"
      console.log "Yes, My Lord.."
      console.log "You are the Queen"
  else console.log "You are nobody"

編譯結果:

1
2
3
4
5
6
7
8
9
10
11
switch (username) {
  case "eddie":
      console.log("You are  the King");
      break;
  case "nayumi":
      console.log("Yes, My Load..");
      console.log("You are the Queen");
      break;
  default:
      console.log("You are nobody");
}

要注意的是,跟if..else一樣,如果是單行敘述,when後面請加上then,否則不需要加。話說還好這點像Ruby不像Python,Python並沒有switch之類的可以用,這並沒不好,只是我還是覺得有switch之類的語法可以讓程式碼變得比較清楚,純個人喜好。

迴圈

CoffeeScript裡的迴圈跟一般的程式語言沒什麼太大的差別,就差不多是那麼一回事。前面提到的Range,常會放在迴圈裡面來使用:

1
2
3
4
console.log i for i in [1..10]                  # 印出1到10
console.log i for i in [1..10] when i % 2 == 0  # 印出2, 4, 6, 8, 10
console.log i for i in [1..10] by 2             # 一次跳2,印出1, 3, 5, 7, 9
console.log i * 2 for i in [1..10]              # 對每個i值 x 2

還有類似Ruby的陣列操作:

1
[1..10].map (i) -> console.log i*2

編譯結果:

1
2
3
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function(i) {
  return console.log(i * 2);
});

跟if/unless一樣,while也有他的死對頭until (while = not until, until = not while),如果while / until的敘述句只有一行的話,同樣也可以放到句子的最後面。

1
console.log "Keep Walking" while miles < 100

編譯結果:

1
2
3
while (miles < 100) {
  console.log("Keep Walking");
}

語法糖衣

為了讓程式碼看起來更像一般的英文敘述句,在CoffeeScript裡特別做了一些alias,例如幫true值做了兩組alias:on以及yes,相對的,off跟no就是false的同義詞,其它的還有:

is    =>  ===
isnt  =>  !==
and   =>  &&
or    =>  ||
not   =>  !

所以,你可能可以寫出這樣的程式碼:

1
2
3
4
5
6
7
8
if light is off
  console.log "I can't see anything"

if eddie isnt handsome
  console.log "It's impossible!"

if girl is not single
  console.log "Don't Touch! Be Careful!"

編譯結果:

1
2
3
4
5
6
7
8
9
10
11
if (light === false) {
  console.log("I can't see anything");
}

if (eddie !== handsome) {
  console.log("It's impossible!");
}

if (girl === !single) {
  console.log("Don't Touch! Be Careful!");
}

這樣一來,程式碼看起來就更口語化了。

最後來看一下這個好用的問號:

1
age ?= 18

這個的意思是說,如果age這個變數還沒被定義或給值的話,就給這個變數設定為18。簡單的說,就是給預設值啦,所以上面這一行會被編譯成:

1
2
3
4
5
if (typeof age !== "undefined" && age !== null) {
  age;
} else {
  age = 18;
};

真的是相當方便啊!!

更多精彩內容,請參考CoffeeScript官網上文件。