|
 |
|
 |
|
 Loading ...
話說其實從大學起就一直都很想學組合語言,但一直都沒能夠成功的進入組語的學習領域。
後來陸陸續續接觸了一些其他的程式語言,玩到 Functional Programming 之後,也漸漸地把學組合語言這件事給拋到腦後了,畢竟光是玩 Functional Programming 就已經玩不玩了說。
只是後來又不小心進到了需要慣 C 的工作領域,重新在 Linux Kerenel 下面打轉,於是學組語這個念頭又回來了--畢竟,這是可是基礎中的基礎啊,而且追 Kernel 常常追到最後都是組語。
但問題是組語要怎麼學呢?我想一定也有很多想學組語,但和我一樣不得其門而入的朋友有相同的困擾。
以我自己而言,我有莫名其妙、不知道為何會出現的,從 80386 時代遺留下來的組語書籍(還用 PE2 咧),有自己去書局買的 NASM 的書,也有資工系開的組合語言課程的課本,甚至是網路上開放下載的教學書籍。
可是以上沒有任何一本書真正讓我進入組語的世界,理由很簡單--這些書我怎麼看都覺得不對勁,不知道該如何寫出我的第一隻組語程式。
有的一開始就和你講什麼 MOVL 是幹嘛用的,ADD 又是啥,但卻又沒有一隻完整的程式可以執行試驗--這樣根本就沒感覺啊!根本就不能從錯誤之中學習啊(例如隨便亂加兩個暫存器會怎樣)!
有的嘛,遵照古老的傳統,一開始就寫一個 Hello World 給你,然後再告訴你不要管那些 include 的黑魔法,反正程式可以跑就好--等一下,我學組語就是為了要了解最底層的運作,結果你叫我不要管他?
總而言之,看這些書的挫敗感真的很大,也因為如此,我一直沒有真正下定決心好好把組合語言給學起來。
一直到前一陣子,我在 Hacking Thursday 的討論區上看到這一本超級棒的書籍--Programming from the Ground Up,這真的是自學組合語言的好物啊!
廢話不多說,我們來看書中第一個程式範例:
.section .data
.section .text
.global _start
_start:
movl $1, %eax # This is the linux kernel system call for exit
movl $0, %ebx # This is the status number return to OS
int $0x80 # This wake up the kernel to run system call
三行程式,而且每一行書裡面都解釋的很清楚(是的,包括 System Call 的部份也有說明,雖然經過簡化與譬喻),沒有任何的黑魔法。
同時,你也可以亂改這個程式,例如試著改變 EBX 暫存器的值,讓他返回不同的值給 Shell,又或者亂改 EAX 裡面的值,然後讓他產生 Segmentation Falut 而當掉。
真是太神奇有趣了!這才叫學組語嘛。
我真的很佩服作者可以想出把程式的結束狀態代碼當成輸出這個點子,完全避開了其他書裡面為了要產生輸出而不得不先使用黑魔法的問題。
再舉另一個例子,他的第二隻程式是介紹控制流程和迴圈,要透過他介紹的各種跳躍指令找到一個數列裡的最大值,這隻程式如下:
.section .data
items:
.long 3, 6, 7, 10, 22, 34, 12, 0
.section .text
.global _start
_start:
movl $0, %edi # move 0 to index register
movl items(,%edi,4), %eax # load the first number
movl %eax, %ebx # put it to the EBX (cureent biggest)
start_loop:
cmpl $0, %eax # check to see if we've hit the end
je loop_exit
incl %edi # increment index by 1
movl items(,%edi,4), %eax # load next number
cmpl %ebx, %eax # compare with current biggest
jle start_loop # jump to start_loop if not bigger
movl %eax, %ebx # else move this value as the largest
jmp start_loop # next turn
loop_exit:
movl $1, %eax # System Call exit (No. 1)
int $0x80 # Singal batman
同樣的,這隻程式也是利用離開狀態做輸出--所以你用到的,都是你學過的東西,沒有黑魔法,每一行每一行都可以解釋到底是在做什麼,讓你驗證你是不是真的了解他。
另外,他用的是 GNU as 的語法,這對我而言有以下幾個好處:
- 這是 Linux Kernel 裡面用的東西,我不用再去熟悉其他語法
- 我只要有一台 Linux Box 就可以試著跑書裡的程式
- 這意謂著你可以用 GCC 把 C 語言編譯到組合語言,然後和這本書裡面的範例做比對,例如講到 Function 的時候,你就可以寫幾個 C 語言函數來驗證書裡講的東西。
所以我一定要大推這一本書的啊~~這本書真的是自學組語的必備良方,只要會一點程式設計,一定可以看得懂的好東西!

Brian Hsu (墳墓) 2010-02-07 (週日) 11:37:18
|
|
 |
|
 |
 |
|
 |
|
 Loading ...
距離脫離尼特族接近兩個星期了,工作方面還在努力學習中,大部份的時間都在追別人寫的東西,只有小修小改而已,希望自己也能加快腳步,盡量能夠早點弄熟自己要接手的東西吶。
回到正題,『遞迴只應天上有、凡人應當用迴圈』這句話應該不少人聽過了,我在很久很久以前也寫過一篇『Haskell 真的比較好懂和不容易出錯嗎?』的文章。
不過,事後證明,我錯了。我接觸了 Scala 之後,我認為這句話很有可能是錯的,遞迴加上 Pattern Matching 或許在某些時候真的比較好懂。
直接來看例子吧,這個例子很簡單:給一個 List,請找出最後一個元素。
程式碼如下:
def last[T] (list: List[T]): T = list match {
case x :: Nil => x // 如果這個值後面沒有東西,他就是目標
case x :: remain => last(remain) // 不然的話繼續處理剩下來的東西
case _ => throw new Exception("opps") // 不可能有這兩種以外的狀況
}
整個程式碼就和用說的一樣簡單容易理解,而另一個遞迴比較好懂的經典例子應該就是費伯納西數列了吧。
def fib (n: Int): Int = {
n match {
case 0 => 0 // 如果是 fib(0) 就是 0
case 1 => 1 // 如果是 fib(1) 就是 1
case x => fib (x-1) + fib (x-2) // 不然的話就是前兩項相加
}
}
幾乎和用嘴巴上數學定義一模一樣。XD
但話說回來,我還是覺得 Haskell 很難懂,理由很簡單--他只給你一種他認為『對』的方法來做事,但他的『對』不見得在每種情況下都是直覺的。
例如在我之前的那一篇文章裡提到的,『把 List 裡數字加總』這件事,如果是在 Scala 裡,可以分別寫出兩種版本:
def sumByIterate (list: List[Int]): Int = {
// 一開始計算機上是 0
var sum: Int = 0
// 一個個把 List 裡的元素加到計算機上
for (x <- list) { sum = sum + x }
// 其實上面那一句你也可以這樣寫
// list.foreach {x => sum = sum + x}
// 最後就是結果
return sum
}
def sumByRecursive (list: List[Int]): Int = {
list match {
case Nil => 0
case x :: xs => x + sumByRecursive(xs)
}
}
我還是覺得 sumByIterate 比較好懂和直覺,三行解決,不用遞回,做的動作就和我們要利用計算機把一個數列給加總一模一樣。
我相信當你要拿計算機加總一個數列的時候,是不會去想『第一個數字再加上其剩下的數字的總合就是答案』這種事,或者去做『從數列最後一個開始往回加』的動作的。
畢竟,加總不就是一個一個把他給打到計算機裡嗎--正是我們第一個版本做的事情。
所以我還是喜歡 Scala 這類 Multi-Paradigm 的程式語言--給你一個完整的工具箱,裡面有各種扳手和鉗子,讓你決定哪一種比較合適,而不是只給一個平口老虎鉗,叫你什麼事都要用這隻老虎鉗來做……

Brian Hsu (墳墓) 2010-02-06 (週六) 22:25:43
|
|
 |
|
 |
 |
|
 |
|
 Loading ...
話說在做單元測試的時候,Mock Object 是很常使用的技巧,可以協助開發者隔離實際的環境,或以人工方式產生錯誤,以加強測試環境的控制。
簡而言之,Mock Object 的精神就是將非你可以控制的部份獨立出來,設計成可以隨時以其他的實作取代。
在 Scala 裡面,要在做單元測試時使用 Mock Object 是相當簡單,只要使用內建的 Trait 就可以很容易的達成 Mock Object 的技巧,而且對於客戶端的使用者而言,並不會感覺到任何使用上的差異。
在這邊,我們使用上次實作的 GeoService 函式庫來做示範,首先先複習一下上次的函式庫(我有做一些小修正,不過介面是一樣的)。
package org.maidroid.utils
import scala.io._
import scala.xml.XML
import scala.xml.Node
import scala.xml.Elem
import java.net.URLEncoder
case class GeoPlacemark (val query: String, val address: String,
val accuracy: Int, val longitude: Double,
val latitude: Double)
{
val longitudeE6 = (longitude * 1E6) toInt
val latitudeE6 = (latitude * 1E6) toInt
}
class GeoService (val query: String, val locale: String)
{
private var statusCode = 0
private var errorMessage = ""
val placemarkList: List[GeoPlacemark] = doQuery ()
def this (query: String) = this (query, "")
def error = (statusCode, errorMessage)
private def createGeoPlacemark (node: Node) =
{
val address = (node \ "address").text
val accuracy = (node \ "AddressDetails" \ "@Accuracy").text
val coordinate = (node \ "Point").text.split (",").toList
val List (longitude, latitude, _) = coordinate
GeoPlacemark (query, address, accuracy.toInt,
longitude.toDouble, latitude.toDouble)
}
private def loadXML = {
val url = "http://maps.google.com/maps/geo?" +
"q=%s&output=xml&gl=%s".
format(URLEncoder.encode(query), locale)
val source = Source.fromURL (url, "utf-8")
val iter = for (line <- source.getLines) yield line
XML.loadString (iter.mkString)
}
private def doQuery () =
{
try {
val xml = loadXML
this.statusCode = (xml \ "Response" \ "Status" \
"code").text.toInt
// code 200 means OK
if (statusCode != 200) {
throw new Exception ("Query Faild")
}
val placemark = xml \ "Response" \ "Placemark"
val addressList = for (node <- placemark) yield
createGeoPlacemark (node)
addressList.toList
} catch {
case e => errorMessage = e.getMessage
Nil
}
}
}
上述的程式碼中,我們可以發現所謂『非我們所能控制』的部份,就是連到 Google Maps 服務取得 XML 文件的 loadXML 這個函式。
這就是我們要把它隔離的程式碼,所以我們先設計一個 Trait,裡面有一個 loadXML 函式的介面。
trait LoadXML
{
protected def loadXML: Elem
}
在這邊要注意的是,由於這個函式是內部使用,不應該透露給外界,所以我們將其宣告為 protected,這和 Java 的介面裡面只能有 public 不一樣。
接著,我們更改我們的 GeoService 的實作,要改的部份只有兩個:
- 讓 GeoService mix-in LoadXML
- 將 GeoService#loadXML 的部份改成 override protected
完整的程式碼如下:
package org.maidroid.utils
import scala.io._
import scala.xml.XML
import scala.xml.Node
import scala.xml.Elem
import java.net.URLEncoder
trait LoadXML
{
protected def loadXML: Elem
}
case class GeoPlacemark (val query: String, val address: String,
val accuracy: Int, val longitude: Double,
val latitude: Double)
{
val longitudeE6 = (longitude * 1E6) toInt
val latitudeE6 = (latitude * 1E6) toInt
}
class GeoService (val query: String, val locale: String) extends
LoadXML
{
private var statusCode = 0
private var errorMessage = ""
val placemarkList: List[GeoPlacemark] = doQuery ()
def this (query: String) = this (query, "")
def error = (statusCode, errorMessage)
private def createGeoPlacemark (node: Node) =
{
val address = (node \ "address").text
val accuracy = (node \ "AddressDetails" \ "@Accuracy").text
val coordinate = (node \ "Point").text.split (",").toList
val List (longitude, latitude, _) = coordinate
GeoPlacemark (query, address, accuracy.toInt,
longitude.toDouble, latitude.toDouble)
}
protected def loadXML = {
val url = "http://maps.google.com/maps/geo?" +
"q=%s&output=xml&gl=%s".
format(URLEncoder.encode(query), locale)
val source = Source.fromURL (url, "utf-8")
val iter = for (line <- source.getLines) yield line
XML.loadString (iter.mkString)
}
private def doQuery () =
{
try {
val xml = loadXML
this.statusCode = (xml \ "Response" \ "Status" \
"code").text.toInt
// code 200 means OK
if (statusCode != 200) {
throw new Exception ("Query Faild")
}
val placemark = xml \ "Response" \ "Placemark"
val addressList = for (node <- placemark) yield
createGeoPlacemark (node)
addressList.toList
} catch {
case e => errorMessage = e.getMessage
Nil
}
}
}
什麼?結束了?!別懷疑,真的就只有這樣子而已,這正是 Scala 吸引我的地方,夠方便吧!
在測試開始之前,我們先來做幾個 Mock 來用吧,要做 Mock 很簡單,只要再宣告繼承 LoadXML 的 Trait 即可。在這次的測試中,我們建造了以下三個 Mock。
- 正確的 XML 回傳值
- 錯誤的 XML 回傳值
- 執行期間發生連線錯誤的 Exception
// 正確的 XML 回傳值
trait MockXML extends LoadXML
{
override def loadXML =
}
// 錯誤的 XML 格式
trait WrongXML extends LoadXML
{
override def loadXML =
}
// 連線錯誤 Exception
trait ExceptionXML extends LoadXML
{
override def loadXML = throw new java.net.ConnectException
}
有了這些 Mock 物件之後,就可以著手來寫單元測試了,在這裡所使用的是 ScalaTest 這個支援多種單元測試風格的 Framework,我們用的是 FlatSpec 這個 Behavior Driven Development 測試。
由於使用 FlatSpec 所寫出的測試都相當直覺,看起來就像英文句子,所以程式碼的部份就不做詳細的解釋了。
在這邊要注意的地方,就是當我們要使用 Mock 的時候,直需要在建立物件時將要使用的 Mock 給 mix-in 進來就好,範例如下。
// 不使用任何 Mock
val noMock = new GeoService ("中央研究院")
// 分別使用上述三種 Mock
val mock1 = new GeoService ("中央研究院") with MockXML
val mock2 = new GeoService ("中央研究院") with WrongXML
val mock3 = new GeoService ("中央研究院") with ExceptionXML
知道了上述的規則後,我們就可以使用這三個 Mock 撰寫我們的測試案例了,完整的測試案例如下。
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import org.maidroid.utils._
class GeoServiceSpec extends FlatSpec with ShouldMatchers
{
// 不使用任何 Mock 物件
"A GeoService" should "retun a list of GeoPlacemark when successed" in {
val service = new GeoService ("中央研究院")
val correct = GeoPlacemark ("中央研究院",
"115 Taiwan Taipei City Nangang "+
"District中央研究院", 9,
121.6122646, 25.0405918)
service.placemarkList should be === List (correct)
}
// 使用 MockXML 以保確取得正確的 XML 回傳值
it should "has statusCode 200 and no error message when successed" in {
val service = new GeoService ("中央研究院") with MockXML
service.error should be === (200, "")
}
// 例用 WrongXML Mock 測試當回傳值為不合格式的 XML 時的狀況
it should "has statusCode 0 and empty list when XML format is wrong" in {
val service = new GeoService ("中央研究院") with WrongXML
service.placemarkList should be === Nil
service.error._1 should be === 0
}
// 例用 ExceptionXML 測試發生 Exception 時的行為
it should "has statusCode 0 and empty list when Exception occurred" in {
val service = new GeoService ("中央研究院") with ExceptionXML
service.placemarkList should be === Nil
service.error._1 should be === 0
}
}
如何?要在 Scala 使用 Mock Object 進行單元測試很方便吧?!請多多利用這個技巧,讓單元測試變得更簡單愉快喲!

Brian Hsu (墳墓) 2010-01-16 (週六) 12:00:10
|
|
 |
|
 |
 |
|
 |
|
 Loading ...
話說 Android 裡的 Googple Maps API 沒有地址反查經緯度的功能,有點小不方便,於是我就自己弄一個簡單的 Scala 函式庫出來了。
這裡簡單介紹一下做法,首先一開始先來定義我們的使用者端介面。在這邊,由於 Google Maps 在查詢地址時返回的是一個列表,所以我們的函式庫也是傳回一個 List。
另外,像很多國家都有『南港』這個地名,為了縮小範圍,我們也定了另一個介面,可以指定查詢的國家。
整體使用的感覺如下:
val sinica: List[GeoPlacemark] = new GeoService ("中央研究院") // 預設查詢
val nangang: List[GeoPlacemark] = new GeoService ("南港", "tw") // 指定國家
println (sinica.placemarkList) // 取得『中央研究院』的相關地理資料
println (nangang.placemarkList) // 取得『南港』的地理資料
在上面的程式裡,GeoPlacemark 的部份就是該地址的地理資訊,我們希望這個物件存放下列幾像資訊。
- 原始查詢字串
- Google Maps 幫我們解析出的詳細地址
- 範圍精確度
- 經度
- 緯度
另外,我們也希望能夠有方便的函式可以幫我們把經度和緯度轉換成 GeoPoint 用的 microdegree。
由這邊可以看出來,將 GeoPlacemark 設計成 immutable 是比較合理的,因為你不應該手動更改任何查詢回來的經緯度結果。此外,在這邊我們需要的都只是單純的取得物件相關的資料,沒有什麼複雜的動作。
這種資料結構,在 Scala 裡最適合使用 case class 來實作了,我們實作出的程式碼如下。
case class GeoPlacemark (val query: String, val address: String,
val accuracy: Int, val longitude: Double,
val latitude: Double)
{
val longitudeE6 = (longitude * 1E6) toInt
val latitudeE6 = (latitude * 1E6) toInt
}
在這段程式碼裡,我們宣告了一個 GeoPlacemark 的 case class,建構子傳入的參數剛好就是上面清單列出來的資料。至於 longitudeE6 和 latitudeE6 因為可以直接從傳入的經緯度推算,所以不用放在建構子中,使用 val 變數而不用 def 定義,是因為這樣這兩個計算只會在建立物件時執行一次而已。
別懷疑,就真的只有這樣而已。剩下的 Scala 都幫你搞定了,包括各個變數的 getter 以及相關的 toString() 和 hashCode() 這類東西,而且還可以用在 Pattern Matching 上!
接著來看我們的 GeoService 類別吧!這邊的重點其實不多,只有幾個而已。
首先,我們要從一個網址取回資料時,只要用 Scala.io.Source 就可以了。
val url = "http://maps.google.com/maps/geo?q=%s&output=xml&gl=%s".
format(query, locale)
val source = Source.fromURL (url, "utf-8")
val iter = for (line <- source.getLines) yield line
val xml = XML.loadString (iter.mkString)
裡面的 iter 會是一個 Iterator,每次一行,可以使用 iter.mkString 轉成字串,接著再用 scala.xml.XML.loadString 把他轉成 XML 資料結構,夠簡單吧?連 HTTP Connection 都不用建立了。如果只是單純的要抓網頁資料,像是 RSS/ATOM 的話,用內建的 io.Source 類別就很足夠了。
接著要檢查回傳值,如果 Google Maps 成功反解地址的話,會傳回 200。
statusCode = (xml \ "Response" \ "Status" \ "code").text.toInt
// code 200 means OK
if (statusCode != 200) {
throw new Exception ("Query Failed")
}
val placemark = xml \ "Response" \ "Placemark"
val addressList = for (node <- placemark) yield
createGeoPlacemark (node)
上面的程式碼裡,我們利用類似 XPath 的方式,取得 Status 下的 code 節點的內容,接著轉成整數,如果不是 200 代表查詢失敗。成功的話,把每一個 Placemark 節點丟到 createGeoPlacemark 裡,建立一個 GeoPlacemark 物件的 List。
private def createGeoPlacemark (node: Node) =
{
val address = (node \ "address").text
val accuracy = (node \ "AddressDetails" \ "@Accuracy").text
val coordinate = (node \ "Point").text.split (",").toList
val List (longitude, latitude, _) = coordinate
GeoPlacemark (query, address, accuracy.toInt,
longitude.toDouble, latitude.toDouble)
}
createGeoPlacemark 沒啥特別的,就是取得 XML 的內容,解析後把他丟給 GeoPlacemark 的建構子,產生 GeoPlacemark 物件而已。
結合上述所有的東西,我們的 GeoService 物件就出來啦。
class GeoService (val query: String, val locale: String)
{
lazy val placemarkList: List[GeoPlacemark] = doQuery ()
private var statusCode = 0
private var errorMessage = ""
def this (query: String) = this (query, "")
def error = (statusCode, errorMessage)
private def createGeoPlacemark (node: Node, query: String) =
{
val address = (node \ "address").text
val accuracy = (node \ "AddressDetails" \ "@Accuracy").text
val coordinate = (node \ "Point").text.split (",").toList
val List (longitude, latitude, _) = coordinate
GeoPlacemark (query, address, accuracy.toInt,
longitude.toDouble, latitude.toDouble)
}
private def doQuery () =
{
try {
val url = "http://maps.google.com/maps/geo?q=%s&output=xml&gl=%s".
format(query, locale)
val source = Source.fromURL (url, "utf-8")
val iter = for (line <- source.getLines) yield line
val xml = XML.loadString (iter.mkString)
statusCode = (xml \ "Response" \ "Status" \ "code").text.toInt
// code 200 means OK
if (statusCode != 200) {
throw new Exception ("Query Faild")
}
val placemark = xml \ "Response" \ "Placemark"
val addressList = for (node <- placemark) yield
createGeoPlacemark (node, query)
addressList.toList
} catch {
case e => errorMessage = e.getMessage
Nil
}
}
}
這樣我們就完成了一個可以用地址來反查經緯度的函式庫了,夠簡單吧?最後,我們發現 doQuery 看起來有點複雜,其實可以把抓網頁轉成 XML 的部份再寫成另一個 loadXMLFromGoogleMaps 函式,所以來重構一下唄!
class GeoService (val query: String, val locale: String)
{
lazy val placemarkList: List[GeoPlacemark] = doQuery ()
private var statusCode = 0
private var errorMessage = ""
def this (query: String) = this (query, "")
def error = (statusCode, errorMessage)
private def createGeoPlacemark (node: Node) =
{
val address = (node \ "address").text
val accuracy = (node \ "AddressDetails" \ "@Accuracy").text
val coordinate = (node \ "Point").text.split (",").toList
val List (longitude, latitude, _) = coordinate
GeoPlacemark (query, address, accuracy.toInt,
longitude.toDouble, latitude.toDouble)
}
private def loadXMLFromGoogleMaps = {
val url = "http://maps.google.com/maps/geo?q=%s&output=xml&gl=%s".
format(query, locale)
val source = Source.fromURL (url, "utf-8")
val iter = for (line <- source.getLines) yield line
val xml = XML.loadString (iter.mkString)
val statusCode = (xml \ "Response" \ "Status" \ "code").text.toInt
(xml, statusCode)
}
private def doQuery () =
{
try {
val (xml, statusCode) = loadXMLFromGoogleMaps
// code 200 means OK
if (statusCode != 200) {
this.statusCode = statusCode
throw new Exception ("Query Faild")
}
val placemark = xml \ "Response" \ "Placemark"
val addressList = for (node <- placemark) yield
createGeoPlacemark (node)
addressList.toList
} catch {
case e => errorMessage = e.getMessage
Nil
}
}
}
如何?看起來清楚多了吧?

Brian Hsu (墳墓) 2010-01-15 (週五) 12:27:54
|
|
 |
|
 |
 |
|
 |
|
 Loading ...
ScalaMap is a wrapper of Android Google Maps API for Scala.
Sorry for my poor English, but I think this may be useful if you wnat develop Android w/ Google Maps API application using Scala.
Overview
Currently, it’s not easy to use Android’s Google Maps API for two reasons.
There is a bug in Scala compiler. If a Java library reference to a static inner class in another Java library, the compiler will complain that 『classfile is broken』 (Scala 2.7.7) or 『Missing dependency』 (Scala 2.8).
- Unfortunately, it is what happened in MapView, it return ViewGroup.LayoutParams in several public methods.
Scala cannot access Java protected static classes / fields / methods.
- That is what happened in ItemizedOverlay, where you should call protected static method boundCenter(Drawable drawable) or boundCenterBottom (Drawable drawable) in subclass’s constructor.
To overcome this problem, I’ve created a simple wrapper library located at this GitHub repository. It’s not prefect and has a lot limitations , but I think it should work.
License
This library is release under GNU Lesser General Public License. See COPYING AND COPYING.LESSER for details.
Install
This wrapper library use SBT (Simple Build Tool) as build system, it is not required, but will make things easier. You should install it.
- Download and install SBT following this instruction, it should be easy.
- Download the source code of ScalaMap.
- Unzip it to a directory called scalamap/
- Copy your android.jar and maps.jar library files to scalamap/lib/
- $ cd scalamap/
- $ sbt package
- After step 7, you should get a JAR file called scalamap-0.0.1.jar under target/ directory.
USAGE
Remember adding scalamap-0.0.1.tar.gz to your classpath when you compile your project.
Take a look at the example code below and project under demo/ directory.
Import org.maidroid.scalamap._
Basically, you could use the following code to create a SMapView object, where R.id.mapview is pointed to a MapView object in layout file.
val sMapView = new SMapView (findViewById (R.id.mapview))
You should be able to use a SMapView object just like an ordinary MapView, but remember, it is a wrapper using composition instead of inheritance, so there are some methods is not implemented in this wrapper.
NOTE
If you need MapController, use sMapView.getSMapController() to get a SMapController wrapper object.
If you need hook listener to a MapView, use sMapView.getViewGroup() to get the MapView object as instance of ViewGroup.
DO NOT USE sMapView.getMapView() / s or you will get a compiler error message like 『class is borken』, YOU’VE BEEN WARNED.
There are two minor API difference:
- The constructor of SItemizedOverlay is (Drawable drawalbe, int mode), where mode is one of
BOUND_CENTER, BOUND_CENTER_BOOTOM, and NO_BOUND in SItemizedOverlay.
- SMapView.setReticleDrawMode takes an arguement of type Int, which is one of the following three value:
SMapView.DRAW_RETICLE_NEVER, SMapView.DRAW_RETICLE_OVER, and SMapView.DRAW_RETICLE_UNDER.
CODE EXAMPLE
This is the code example corresponding to the tutorial Hellp, MapView on Android Developer site.
package org.bone;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.widget.ZoomControls;
import android.widget.LinearLayout;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.OverlayItem;
import com.google.android.maps.Overlay;
import org.maidroid.scalamap.SMapView
class HelloMap extends MapActivity
{
private lazy val layout = findViewById (R.id.zoomview).
asInstanceOf[LinearLayout]
private lazy val mapView = new SMapView (findViewById(R.id.mapview))
private lazy val mapOverlays = mapView.getOverlays
private lazy val drawable = getResources.
getDrawable (R.drawable.androidmarker)
private lazy val itemizedoverlay = new HelloItemizedOverlay (drawable)
override def isRouteDisplayed = false
override def onCreate(savedInstanceState: Bundle)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
mapView.setBuiltInZoomControls (true)
val point1 = new GeoPoint (19240000, -99120000)
val point2 = new GeoPoint(35410000, 139460000)
val overlayitem1 = new OverlayItem (point1, "", "")
val overlayitem2 = new OverlayItem (point2, "", "")
itemizedoverlay.addOverlay (overlayitem1)
itemizedoverlay.addOverlay (overlayitem2)
mapOverlays.add (itemizedoverlay)
}
}
package org.bone
import android.graphics.drawable.Drawable
import com.google.android.maps.OverlayItem
import java.util.ArrayList
import org.maidroid.scalamap.SItemizedOverlay
import SItemizedOverlay.BOUND_CENTER_BOTTOM
class HelloItemizedOverlay (drawable: Drawable) extends
SItemizedOverlay[OverlayItem] (drawable, BOUND_CENTER_BOTTOM)
{
private lazy val mOverlays = new ArrayList[OverlayItem]
override def createItem (i: Int) = mOverlays.get(i)
override def size () = mOverlays.size()
def addOverlay (overlay: OverlayItem)
{
mOverlays.add (overlay);
populate ();
}
}

Brian Hsu (墳墓) 2010-01-14 (週四) 19:55:41
|
|
 |
|
 |
 |
|
 |
|
 Loading ...
話說這兩天想到一些 Android 加 Google Maps 的應用,於是試著想用 Scala 試著做做看,沒想到竟然遇到了一堆問題,以致於陷入了苦戰之中。
簡單的來講,造成這次的狀況的原因,有以下兩個
- Scala 的編譯器有蟲,當某個 Java 函式庫參照到另一個 Java 函式庫的 static inner class 時,就會找不到而爆炸。這次爆炸的原因就是 MapView 裡面用了 ViewGroup.LayoutParams。
- Scala 因為語意上的關係,沒有 static 這種東西,以致於無法存取 static protected 的方法,所以 ItemizedOverlay 又不能用了。
結論:兩個最重要的東西都不能用啊!
為了解決這個問題,我只好自己寫一個 Wrapper,並且把他包成函式庫的形式,方便以後使用,或者以防有人遇到和我一樣的狀況卻求助無門,程式碼和說明在 GitHub 上。
總之,目前總算是把 Hello, MapView 的範例跑起來了,程式碼如下,看起來和原來 Java 的版本差不多,這樣的 Wrapper 應該算還可以吧?
package org.bone;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.widget.ZoomControls;
import android.widget.LinearLayout;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.OverlayItem;
import com.google.android.maps.Overlay;
import org.maidroid.scalamap.SMapView
class HelloMap extends MapActivity
{
private lazy val layout = findViewById (R.id.zoomview).
asInstanceOf[LinearLayout]
private lazy val mapView = new SMapView (findViewById(R.id.mapview))
private lazy val mapOverlays = mapView.getOverlays
private lazy val drawable = getResources.
getDrawable (R.drawable.androidmarker)
private lazy val itemizedoverlay = new HelloItemizedOverlay (drawable)
override def isRouteDisplayed = false
override def onCreate(savedInstanceState: Bundle)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
mapView.setBuiltInZoomControls (true)
val point1 = new GeoPoint (19240000, -99120000)
val point2 = new GeoPoint(35410000, 139460000)
val overlayitem1 = new OverlayItem (point1, "", "")
val overlayitem2 = new OverlayItem (point2, "", "")
itemizedoverlay.addOverlay (overlayitem1)
itemizedoverlay.addOverlay (overlayitem2)
mapOverlays.add (itemizedoverlay)
}
}
package org.bone
import android.graphics.drawable.Drawable
import com.google.android.maps.OverlayItem
import java.util.ArrayList
import org.maidroid.scalamap.SItemizedOverlay
import SItemizedOverlay.BOUND_CENTER_BOTTOM
class HelloItemizedOverlay (drawable: Drawable) extends
SItemizedOverlay[OverlayItem] (drawable, BOUND_CENTER_BOTTOM)
{
private lazy val mOverlays = new ArrayList[OverlayItem]
override def createItem (i: Int) = mOverlays.get(i)
override def size () = mOverlays.size()
def addOverlay (overlay: OverlayItem)
{
mOverlays.add (overlay);
populate ();
}
}

Brian Hsu (墳墓) 2010-01-13 (週三) 22:35:29
|
|
 |
|
 |
 |
|
 |
|
 Loading ...
嗯,剛好在『PHP 很爛』這篇文章裡看到了這個 Recovery From Addiction 的影片。
基本上關於 PHP 爛不爛的部份每個人都有想法,但基本上我是論認為 PHP 很爛的那一派,我的碩士論文是實作 Drupal 的一個模組,用的是 PHP,可是我還是覺得 PHP 很爛。
但不可否認的是,在我學 Scala 之前,要做用了就丟的東西時候我的第一個選擇會是 PHP(因為我沒學 shell scrip),簡單,好用,用了就丟的東西也不用講究什麼安全性或強固性,可以動就好了。
但自從學了 Scala 之後,Scala 會是我的首選,再不然,我也會選 Ruby,雖然我的 Ruby 程度大概還只停留在 Hello World 而已。
回到正題,Recovery From Addiction 是一個很棒的影片,不過可惜的是裡面的例子只有提到 Java vs Python 而已,但是個人私心很喜歡 Scala 還有 Ruby ,所以就在這裡補上 Ruby 和 Scala 例子。
想從 Java 跳船的人可以參考一下 Recovery From Addiction 影片,以及這些例子。
雖然影片是從座標的函式庫的設計開始講,不過這裡會從客戶端開始說。
我們先看 Java / Python / Ruby / Scala 三者的客戶端程式。
// This is Java.
Coord coord = new Coord ();
coord.lat = -93.0 // Souther than south!
# This is Python.
coord = Coord ()
coord.lat = -93.9 # Souther than south!
# This is Ruby.
coord = Coord.new
coord.lat = -93.9 # Souther than south!
// This is Scala.
val coord = new Coord ()
coord.lat = -93.0F // Souther than south!
基本上都是一樣的,產生一個 Coord 物件,然後指定一個座標。接下來,我們來看第一版的函式庫端。
// This is Java.
public class Coord {
public float lat;
public float lon;
public Coord () {}
public Coord (float lat, float lon) {
this.lat = lat;
this.lon = lon;
}
}
# This is Python.
class Coord (object)
def __init__ (self, lat = 0.0, lon = 0.0):
self.lat, self.lon = lat, lon
# This is Ruby.
class Coord
attr_accessor :lat, :lon
def initialize(lat = 0.0, lon = 0.0)
@lat = lat
@lon = lon
end
end
// This is Scala.
class Coord (var lat: Float, var lon: Float) {
def this () = this (0, 0)
}
到這邊為止,Python / Ruby / Scala 還是不算完全打敗 Java,畢竟 Java 也才九行程式碼,Ruby 也要七行。
重點是下面了,現在我們發現原來的設計有問題,因為 lat 不能小於 -90 或是大於 90 ,我們要如何在不更動原有的客戶端的情況下,把這個判斷加到我們的函式庫呢?Python / Ruby / Scala 都可以很輕鬆的做到,但 Java 就不行了。
先看 Python 的:
class Coord (object)
def __init__ (self, lat = 0.0, lon = 0.0):
self.lat, self.lon = lat, lon
def getLat (slef):
return self.__lat
def setLat (self, lat):
if not -90.0 <= lat <= 90.0:
raise ValueError ("Bad latitude")
self.__lat = lat
lat = property (getLat, setLat)
這段程式碼裡面,我們設了 getLat 和 setLat 這兩個函式,接著再指定說 lat 的 getter / setter 就分別是 getLat 和 setLat。
這個則是 Ruby 的:
class Coord
attr_accessor :lon
attr_reader :lat
def initialize(lat = 0.0, lon = 0.0)
@lat = lat
@lon = lon
end
def lat=(lat)
if (lat < -90 || lat > 90)
raise "Bad latitude"
end
@lat = lat
end
end
在 Ruby 裡的做法,則是叫 Ruby 自己生一個 getter 給我們,然後我們再定義 lat=(lat) 這個函數,而這個函數就是 lat 的 setter。
接著看 Scala 的做法:
class Coord (var mLat: Float, var lon: Float) {
def this () = this (0, 0)
def lat = mLat
def lat_= (lat: Float) {
if ( lat < -90 || lat > 90 )
throw new Exception ("Bad latitude")
mLat = lat
}
}
在 Scala 裡,我們也是自己定義了 lat 的 getter / setter,眼尖的朋友可能發現了原來成員變數的 lat 現在變成了 mLat,這是 Scala 本身的限制,因為 Scala 會幫我們產生一組 getter / setter,所以為了必免命名衝突,得用另一個名稱。
所幸的是,Scala 是靜態語言,所以你大可先改成員變數的名稱,然後叫編譯器幫你抓出所有錯的地方,基本上很簡單就可以做程式碼的重構,很少遇到什麼大問題。
另外,Scala 神奇的地方,就在於雖然他寫起來向 Ruby / Python 這種動態型別的程式語言,可是實際上他是靜態型別的,所有的型別錯誤在編譯時期就會被抓出來。
我完全不會 Python、Ruby 是初學、Scala 是漸漸上手,在這邊野人獻曝一下,分享一下 Ruby 和 Scala 的程式碼。
順道一提,影片中有提到 Ruby 的 Unicode 問題,但如果我記得沒錯的話,目前 Ruby 應該是支援 Unicode 了。
然後,雖然作者有說他不喜歡 Ruby 那些從 Perl 借來,看起來很像暗碼的變數符號,但我自己就整個程式的觀點來看,其實我發現只要掌握了一些大的原則,Ruby 的程式碼比 Python 更容易看懂。
舉一個實際一點的例子,我就看不出來為什麼 Python 裡成員函數要存取成員變數的時候要用 self.__lat 這個方式,不清楚為什麼 lat 前面還要多兩個底線。
相較之下,Ruby 只是用了變數開頭的符號來區別是哪種變數罷了,就程式的閱讀和撰寫來說,和其他語言感覺沒有太大的隔閡。
當然,這只能說是個人的喜好就是了,畢竟 Python / Ruby 兩者本身所信仰的 coding 哲學本來就不同,只能說我比較偏好 Scala / Ruby 這邊的設計哲學而已。
最後的最後,其實我很訝異最後 Scala 的版本竟然會比 Python / Ruby 還來得少行,再加上還是靜態型別,可見 Scala 狂勝 Python / Ruby !(大誤)
PS、程式語言比較的東西,看看就好,用自己順手,喜歡的工具最重要!

Brian Hsu (墳墓) 2010-01-07 (週四) 23:16:12
|
|
 |
|
 |
 |
|
 |
|
 Loading ...
這是接續之前寫的:『可以 click 的 ScrollView』,因為之前的版本是有問題的!
說實話,這是寫 Android 寫到現在被搞最久的一次了。之前大部份都還滿順手的,遇到的問題 Google 一下也大部份都有人問過,或是提供了解答的線索。
但這次就不同了,被搞了超久,追了半天才找到問題所在,終於做出我想要的東西出來了。
簡單來講,為了要實作出理想的 Maidroid 系列,我必須要有一個元件是用來當做對話框的,就像 AVG 遊戲裡的對話框一樣,是用來讓程式裡的妺斗們與主人互動的。
在實作上來講,就是要有一個區塊能夠顯示對話,而當使用者按下去時,能夠顯示下一句的對話。
看起來好像很簡單,用 TextView 就可以做到了,但實際上還有另外一樣需求,就是當文字超出範圍時,要能夠出現捲軸,讓使用者捲動。
合理的選擇是用 ScrollView 把 TextView 包起來。因為如果單純地使用TextView 的話,會有很麻煩的效果出現,而且我不知道為什麼,雖然可以捲動,但 Scroll Bar 就硬是不給我出現。
但這一包,問題就一大堆了。
包話 View.OnClickListener 完全不能用,所以必需自己用其他方式來判斷是否是 Click 的動作。
本來以為用 OnTouchEventListener 就好了,沒想到用下去,只有當對話框出現捲軸時才有用,追了好幾天,試了好幾種方法,才終於找到一個非常不起眼的原因--在 ScrollView 的實作裡頭,如果這個 View 沒有出現捲軸,他會把除了 ACTION_DOWN 之外的 MotionEvent 全部給吃光光!
就為了這個奇怪的行為,浪費了我好幾天啊!我可以理解如果沒有捲軸你就不需要處理,可是也沒必要把 Event 吃掉吧!?(淚)
所以,以下終於是正確可動的版本了,另附轉換特效,以及給其他人在最後一個訊息時可以做出反應的 Inject Method。
順道一題,這是 Scala 的版本,Java 的話請自行轉換吧,反正整個流程和概念是一樣的。
不過話說回來,我還是不確定這是不是最恰當的實作方式就是了(特別是在是否為 Click 的判斷上),但至少它可以動。XD
也就是說,停滯了這麼多天,我終於可以繼續進行下一步了。orz…
import android.content.Context
import android.view.animation.Animation
import android.view.animation.Animation.AnimationListener
import android.view.animation.AnimationUtils._
import android.view.View
import android.view.MotionEvent
import android.widget.ScrollView
import android.widget.TextView
import android.util.AttributeSet
import android.util.Log
class ViewMessageBox (context: Context, attrs: AttributeSet) extends
ScrollView (context, attrs)
{
// Call Initialzation when construct
init ()
/*=================================================================
* Private Fields
*===============================================================*/
private lazy val MAX_OFFSET = 1
private lazy val textView = new TextView (context)
private var messageList: List[String] = Nil
/*=================================================================
* Initialization Routine
*===============================================================*/
private def init ()
{
textView.setTextSize (20)
addView (textView)
this.setOnTouchListener (onTouchListener)
}
/*=================================================================
* Private Methods
*===============================================================*/
/**
* 用來檢查是否有 ScrollBar 出現,測試的標準是當 TextView 比
* ScrollView 來的大時,就當做有 ScrollBar。
*/
private def hasScrollBar = textView.getHeight > this.getHeight
private def switchToMessage (message: String, next: List[String])
{
def updateTextView (message: String)
{
val ARROW = "\u25bc"
val DIAMOND = "\u25c6"
val endSymbol = if (next == Nil) DIAMOND else ARROW
textView.setText (message + endSymbol)
}
def prepareAnimation (): Animation =
{
val fadeIn = loadAnimation (context, android.R.anim.fade_in)
val fadeOut = loadAnimation (context, android.R.anim.fade_out)
fadeOut.setAnimationListener (new AnimationListener () {
def onAnimationEnd (animation: Animation) {
updateTextView (message)
textView.startAnimation (fadeIn)
}
def onAnimationRepeat (animation: Animation) {}
def onAnimationStart (animation: Animation) {}
})
fadeOut
}
val animation = prepareAnimation ()
textView.startAnimation (animation)
}
/*=================================================================
* Callbacks
*===============================================================*/
/**
* 重點在這裡。
*
* Click 的定義:壓下與放開的點誤差不超過 1
*/
private lazy val onTouchListener = new View.OnTouchListener ()
{
// 壓下起始座標
var startX = 0.0
var startY = 0.0
def onTouch (view: View, event: MotionEvent): Boolean = {
// 如果沒有 ScrollBar 出現,除了 ACTION_DOWN 之外
// 的 Event 都會被吃掉,所以直接當做 Click 處理。
if (!hasScrollBar) {
showNextMessage ()
return false
}
// 如果有 ScrollBar,先記錄起始下壓點在哪,如果按下
// 與放下的點 X 與 Y 軸均不超過 MAX_OFFSET ,就當做
// 是 Click 事件。
event.getAction match {
case MotionEvent.ACTION_DOWN =>
startX = event.getX
startY = event.getY
case MotionEvent.ACTION_UP =>
if (Math.abs(startX - event.getX) < MAX_OFFSET &&
Math.abs(startY - event.getY) < MAX_OFFSET) {
showNextMessage ()
}
case _ =>
}
return false
}
}
/*=================================================================
* Public Field / Method
*===============================================================*/
// 當最後一個訊息被顯示時會呼叫這個函式
var injectWhenLast: () => Unit = () => {}
def setMessageList (message:List[String])
{
messageList = message
showNextMessage ()
}
def showNextMessage ()
{
// 將 TextView 設成 List 裡的第一個字串
messageList match {
case message :: next =>
switchToMessage (message, next)
if (next == Nil) {
injectWhenLast ()
}
messageList = next
case _ =>
}
}
}

Brian Hsu (墳墓) 2009-12-18 (週五) 19:37:50
|
|
 |
|
 |
 |
|
 |
|
 Loading ...
話說 Android 裡的 ScrollView 是不能 click 的,但為了模擬出 AVG 裡的對話框效果,又想要讓文字超過對話框大小時自動出現捲軸功能,於是就用下面的奇技淫巧做了一個可以 click 的 ScrollView。
然後在這裡 click 的定義是按下的點與最後放開的點,X/Y 軸的距離各不超過 MAX_OFFSET ,至於這個值要用什麼比較好,目前還在測試。
import android.content.Context
import android.util.AttributeSet
import android.widget.ScrollView
import android.widget.TextView
import android.view.View
import android.view.MotionEvent
import android.util.Log
class ViewMessageBox (context: Context, attrs: AttributeSet) extends
ScrollView (context, attrs)
{
init ()
private lazy val textView = new TextView (context)
private val MAX_OFFSET = 1
private def init ()
{
textView.setText ("Hello World")
textView.setTextSize (20)
addView (textView)
this.setClickable (true)
this.setFocusable (true)
this.setOnTouchListener (onTouchListener)
}
/**
* 重點在這裡。
*
* Click 的定義:壓下與放開的點誤差不超過 1
*/
private lazy val onTouchListener = new View.OnTouchListener ()
{
// 壓下起始座標
var startX = 0.0
var startY = 0.0
def onTouch (view: View, event: MotionEvent) = {
event.getAction match {
case MotionEvent.ACTION_DOWN =>
startX = event.getX
startY = event.getY
case MotionEvent.ACTION_UP =>
if (Math.abs(startX - event.getX) < MAX_OFFSET &&
Math.abs(startY - event.getY) < MAX_OFFSET) {
Log.e ("qqq", "Clicked[")
}
case _ =>
}
false
}
}
}

Brian Hsu (墳墓) 2009-12-17 (週四) 09:08:55
|
|
 |
|
 |
 |
|
 |
|
 Loading ...
好吧,雖然是很簡單的一件事,但我不知道為什麼我看的 Scala 的書和文件裡都沒有提到這些事,或許這可能不是很重要,又或者是明顯到根本不需要特別提?
不過,我還是寫一下好了……
話說像 Ruby 這類動態型別程式語言,大部份都有一個有趣的特性,那就是陣列裡面隨便你放任何東西。
舉例來說,下面的陣列在 Ruby 裡是合法的:
arr = [1, 3.14159, "我是字串", Time.now]
然而,像在 C / C++ / Java 這類靜態型別程式語言當中,陣列通常會與型別掛勾,陣列裡只能放相同型別的東西,例如下面的陣列,就只能放整數了。
int x [] = new int[10] // 我只能放整數
這造成了不小的限制,但幸好在 Java 中,由於所有物件都是 Object 的子類別,所以你還是可以像這樣繞道而行,只是程式碼會變很醜而已,而且還得自己想辦法轉型。
// 我能放任何東西,可是請你自己轉型,順道一提,出了錯我不賠錢的
Object x [] = new Object[10]
而在 Scala 之中,一切都變得很簡單了,你有兩個選擇--Structural Type 與 Pattern Matching。
Structural Type 的想法其實就是 Duck Type 的另一種說法罷了--如果他叫起來像鴨子,走起路來也像鴨子,那麼他就是鴨子。
簡單來講,就是我只關心他有沒有實作某個方法或屬性,管他這個方法是哪裡來的,管他是繼承哪個類別或介面,只要他有這個方法或屬性就可以囉!
這麼一來,我就可以宣告一個陣列,裡面放的都是具有 myPrint() 這個方法的物件,但彼此並不互相繼承,也沒有共同的父類別或介面。
這麼一來,我就擁有一個比 C/C++/Java 彈性一點,又比 Ruby 安全一點的陣列(Ruby 可不可以有這樣的限制我不知道,煩請熟悉 Ruby 的朋友幫忙補完),因為我確定我在呼叫陣列裡的每個元素的 myPrint() 方法的時候,絕對不會出錯,一定找得到這個方法。
至於 Pattern Matching 的部份,其實和 Object [] 的概念是一樣的,只是語法簡潔很多,而且在進行型別比對時就幫你轉型,所以你可以很放心不會不小心寫錯。另外,程式碼會漂亮很多,從此脫離 if/else 的魔掌了(大誤)!
以下就是一些實際的程式碼啦,基本上應該有點 Java + Ruby 的基礎就看得懂了,主要的重點大概就是:
- Any 相當於 Java 與 Ruby 中的 Object,是一切物件的父類別。
- [T] 是 Generic Type,把他想成 Java 裡的 <> 就好了,例如 List[T] 就是 List<T>。
- list1.foreach () 就等於 Ruby 的 list.each {|x| ….},只是 x 的部份直接省略為 _ 。
- match 相當於 Java 的 switch,只是他可以連型別一起比對,並且直接幫你轉型,所以當 case x: ForInt 為真時,x 會轉型成 ForInt 。
/***************************************************************/
/* 簡單定義一些沒有共同父類別的東西 */
/***************************************************************/
case class ForInt (x: Int) {
def myPrint () = println ("MyPrint:" + x)
}
case class ForDouble (x: Double) {
def myPrint () = println ("MyPrint:" + x)
}
case class ForString (x: String) {
def myPrint () = println ("MyPrint:" + x)
}
/***************************************************************/
/* 利用 Strucural Type 來宣告 List */
/***************************************************************/
// 注意以下三個並沒有共通的父類別
val forInt: ForInt = new ForInt (3)
val forString: ForString = new ForString ("Hello World")
val forDouble: ForDouble = new ForDouble (3.14159)
// 這個 List 可以放入任何具有 myPrint(): Unit 方法的物件,不論他
// 們是否有共同的父類別,或實作相同的介面。
//
// 第二個 List[T] 是必要的,不然 Type Inference 機制會分析錯誤,
// 造成型別不符的錯誤。
type T = {def myPrint(): Unit}
val list1: List[T] = List[T] (forInt, forString, forDouble)
// 依序呼叫 List 裡每個元素的 myPrint() 方法
list1.foreach (_.myPrint())
/***************************************************************/
/* 以 Pattern Matching 實作隨便你放任何東西的 List */
/* 於執行期再依物件類別決定要做啥 */
/***************************************************************/
val list2: List[Any] = List (1, "我是字串", 3.45, new ForInt(3))
list2.foreach ( _ match {
case x: Int => println ("我是 Int:" + x)
case x: String => println ("我是字串:" + x)
case x: ForInt => println ("我是 ForInt:" + x)
case x => println ("我是其他東西:" + x)
})

Brian Hsu (墳墓) 2009-12-16 (週三) 11:59:48
|
|
 |
|
 |
|
|
|