#導覽列 #文章列表 #許洛豪
在光之翼的守護下訴說著一段段的英雄傳說。 [ 首頁 | 網誌 | 相簿 | 留言 | 訂閱 ]

Recovery From Addiction,加上 Ruby / Scala。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... 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、程式語言比較的東西,看看就好,用自己順手,喜歡的工具最重要!

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2010-01-07 (週四) 23:16:12

重返二十號倉庫。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

昨天日記忘了寫了,星期六的時候喵家台中聚餐,想當然爾,到了台中就一定要到二十號倉庫轉轉!所以我就提早一小時到達台中,跑去那兒晃悠了。

沒有太大的改變,展場還是一樣的粗曠,但可惜的是展場旁那一間曾經在我的學生生涯裡陪伴過我不少次的咖啡廳收掉了,變成了看起來有些許雜亂的倉庫,有一些感傷的氣味存在。

至於這次遇到的展覽嘛……是駐站藝術家的期末展。

對於沒啥藝術天份和欣賞眼光的我來說,大部份都是些看不懂的東西,唯一覺得比較有趣的,是一個『狀況L』的作品,算是裝置藝術和影片的合體吧。

一個戴著安全帽,看起來像來自外星球或未來的女孩子,出沒在台中車站地下道跳舞的場景,影片不長,意味也不明,但就是讓我感到有趣地把它給看完了。

不過話說回來,在二十號倉庫看到過印象最深,覺得最有趣的一份作品果然還是當初二零零四年愛情特展時展出的『愛情便利店』吶!店內各種充滿創意的愛情商品和小遊戲,還有愛情籤筒等有趣的東西。

記得當初是抽到了這麼一隻籤:『財中漸漸見分明,花開花謝結子成,寬心且看月中桂,朗君即便見太平。』

至於這隻籤到底準不準,我也沒印象了,或許,求籤的當下也並沒有什麼真正想實現的願望吧?因為那時我我已經幸福到無以復加了。

但就像舞台劇一樣,這種東西都只能在當下品嚐,所以在幸福當中,總是會有一絲絲的不捨和惆悵,就像現在,我還是好想好想再看一次『如夢之夢』啊!表坊都不出DVD……>_<

其實還想看表坊的『如夢之夢』,但那個票價實在不是我能負擔的起,我又不想坐太差的位。而外表坊的恐怖酒吧是在台北新舞台,所以,看起來我還是繼續無趣的生活唄,除非讓我有什麼意外之財,跑去台北看如夢之夢。

畢竟,沒想到在二零零五年四月四日留下下面這段文字的我,卻真的在二零零四年五月一日花了將近約半個月生活費的三千五佰大洋,跑去看如夢之夢了,而且還是坐蓮花池最好的位置!

如果你問我值得嗎?我會說再值得不過了……我很慶幸我有去看啊。即便當戲落幕,我走出國家戲劇院的那一刻,莫名卻又無比愁悵的情緒就如同排山倒海般往身上襲來,讓人有些受不了。

從前我不懂那是怎麼回事,不過最近我想通了--因為有些事就是單程車票,只能往前走,是回不了頭的。所以這才令人愁悵,因為不論經歷了多少事情,我仍然想要回到國小那段快樂歲月,將人生重新來過。

所以,我才獻慕『重返少年時』裡的堂本剛。所以,在別人的眼裡我似乎總是太沉溺於過往。

但這終究是不可能的,所以,我想是時候該做些改變,下定決心往前走,開始對真面對將來,以及真實的自己的時候了。

所以,接下來的第一步,就是找個幾天,整理一下資料,準備投稿明年四月的 OSDC.tw 了!畢竟,站到這類的舞台上進行講演,一直都是我的夢想之一啊!

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2009-12-29 (週二) 19:41:18

退伍一個月。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

不知不覺中竟然退伍滿一個月了啊!日子過得莫名其妙的快,和在軍中渡日如年的日子完全不同,感覺時間不夠用啊。

至於這一個月來都在做些什麼呢?當然,除了最最最重要的求職活動是跑不掉之外,其實也跑了滿多地方,可能是不知不覺中就想把當兵的鬱悶一股惱的發洩出來吧!

從圖書館搬一大堆書,地下街、研討會、Ruby Tuesday、CWT 23、買了大神和水晶編年史,雖然怎麼看都像是個阿宅的生活,但感覺就是和軍中不一樣啊!

另外,前幾天喵家聚會,去吃了生平第一次吃的野宴,這才知道原來燒肉會發光(大誤)!

回到正題,其實這一個月來除了求職活動外,心力都放在 Maidroid 系列的 Reminder 上了,幾乎開著電腦就是在寫程式,在中間也花了很多時間跌跌撞撞的做各種實驗,而現在主要的功能總算大致上都完成囉!

另外,這兩天也總算幫 Maidroid 系列搞了一個簡單的官方網站

但是……接下來才是難關啊!要準備開始找找看有沒有繪師和配音員可以幫忙了說,畢竟,我寫程式可以,但叫我畫圖可就要我的命囉!但是這一系列的東西,沒有萌圖和萌聲音,就失去意義了啊!XD

另外,由於這個一個月幾乎都在寫程式,於是不論是櫻花大戰四、秋之回憶合輯、空之軌跡 3rd、大神和水晶編年史都還是堆放的狀態,好想變得就像宅方說的:『總之先買下來就安心啦!』的感覺,而且拜此所賜,我的退伍金快花光了,囧。

最後,看到掌櫃的日記,我只覺得地球好可怕,我要回火星去。前一陣子我也才被追問說幹嘛不交女朋友,我總不能回答說,因為我想攻略澪和梓喵吧!(大誤)

唔…… K-On 果真是百合滿滿的神之作啊!

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2009-12-28 (週一) 20:25:52

[Android] 終於從無間 ScrollView / TextView 地獄回來了!

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... 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 _ =>
        }
    }
}
將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2009-12-18 (週五) 19:37:50

[Android] 可以 click 的 ScrollView。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... 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
        }
    }

}
將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2009-12-17 (週四) 09:08:55

懷念 Ruby 隨便給你放的陣列嗎?咱家的 Scala 也有!

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... 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)
})
將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2009-12-16 (週三) 11:59:48

CWT23 與 APH 的 Scala DSL。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...
灣娘卡灣娘卡

警告:本文內含 APH  同人與技術混合內容,APH 為一國家擬人化創作,內容不代表真實國家情形及立場。

話說今天去了 CWT23  的攤位,結果都沒看到我原本萌的東西啊,然後在會場被萌到的大概就是灣娘吧,滿滿的灣娘在路上走來走去,真是萌煞人也。

於是,兩天的購物內容就都變成和灣娘有關的東西是也!而今天入手的就是這套 APH  紙牌遊戲,購買的原因……有阿呆毛的灣娘好萌(大誤)!

好吧……回到正題,其實買的時候很掙扎的,僅管我承認我是個阿宅,可是要在會場上像斑目那樣子的買法,我還是沒辦法的啊,更何況我還是個米蟲,還在等上工呢。

所以,在會場的時候,一直在和另一攤的萌獸DNA與戀星這兩套自製遊戲的合輯考慮,而最後還是選了灣娘卡。

理由嘛,其實是因為我覺得這種卡片遊戲好像還滿適合做成手機連線對戰遊戲的,所以打算買回來研究一下!

然後呢……回家後就不小心搞出這個 DSL  了,花費時間,好像十分鐘不到吧!結論就是 Scala  好強好厲害,最後一段設定遊戲的部份,根本就不像程式語言,可讀性莫名其妙的強,搞不好一般人都看得懂。XD

/**
 *  人物卡片擁有三個屬性:名稱、生命值、描述
 */
case class PersonCard (name: String, description: String, hp: Int) 
{
    def hasHP      (hp: Int) = PersonCard (name, description, hp)
    def describeAs (description: String) = 
        PersonCard (name, description, hp)
}

/**
 *  每個遊戲裡有複數張人物卡片
 */
case class GameBoard (title: String, 
                      personCards: List[PersonCard]) 
{
    def hasPersonCards (cards: PersonCard*) = 
        GameBoard (title, cards.toList)
}

/**
 *  神奇的轉型函式,DSL 的秘密就在這裡
 */
implicit def str2PersonCard (name: String) = 
    PersonCard (name, "", 0)

implicit def str2GameBoard (title: String) = 
    GameBoard (title, Nil)

/**
 *  今天在 CWT23 買的 APH 同人遊戲裡的一些國家卡片
 */
"APH 紙上遊戲" hasPersonCards (

    "台灣" hasHP 60  describeAs "一般來說,中國、香港、日本、韓國、台灣" +
                                "裡,台灣最好相處(因為太弱啦),雖然自" +
                                "己不覺得,但是大家都覺得她家的夜市很好" +
                                "玩。",

    "香港" hasHP 70  describeAs "被英國領養了一段時間,最近回大哥家住," +
                                "有點適應不良,對金錢很精明,但人際關係" +
                                "則有點無力,看不大出來他在想什麼。",

    "美國" hasHP 100 describeAs "熱血青年,自認是世界的警察,對於做家務" +
                                "有點懶洋洋,不過墨西哥會從後院的洞偷溜" +
                                "進來工作所以沒關係,是最近一次大流行感" +
                                "冒的病源。"
)
將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2009-12-13 (週日) 19:19:41

我還是覺得這是基礎中的基礎。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

算是 BBS 上的舊文重 PO 吧,畢竟這是自己的一些想法,留在部落格上好像比較好一點。

原先這篇文章是回應 aMaa 網友的這一篇『類別的方法中為什麼可以建立本身類別的物件』的問題,看起來好像沒啥關聯,但我覺得這還是很重要的,至於後續還有一些發展以及想法,我會另外用專文來寫。

簡單的講,我還是覺得這些看似不相干的基礎是很重要的。

當然,如果你寫程式的目的只是為了好玩,只是想做玩具,只想當成手邊解決問題的工具,那麼這些確實可以不用去弄懂它。

但如果你想靠這行吃飯,我覺得還是認命一點,把基礎搞好吧……

就像,你可以拿起鎚子、釘子、木板自己蓋出一間狗屋,但你應該不會想要去住沒有建築基礎專業知識的設計師、建築師蓋出來的房子吧?


【離題】

說實話,這個討論串讓我想到之前在自己的部落格上寫到的這一篇雜記裡關於程式語言學習的部份,有問到到底在學習程式式語言的時候,要從何處入手。

這一串討論看下來,看來看去,只有一個想法:會有這種疑問,根本就是因為不了解整個 von Neumann架構/程式語言/編譯器/機器語言/虛擬機器之間的關連所造成的嘛……

以下,可能很長,可能很多看似無關緊要的東西(就算你不懂,你的程式還是可以動),可是我自己認為這是值得去了解的基礎,如果你真的用心了解下面所講的東西,基本上就不會再出現類似的疑惑了。

【回題】

其實原 PO 的問題本質上和另一個問題一樣:為什麼像是下面的遞迴程式裡,第 6 行的時候,明明 sum 還明有定義完,卻可以呼叫自己呢?

// 用遞迴計算 1 + 2 + ... + n
int sum (int n) {
   if (n == 1) {
       return 1;
   }

   return n + sum(n-1);
}

為什麼下面的程式裡,Node 明明還沒定義完,裡頭卻又出現另一個 Node 呢?

class Node {
   private Node next;
}

我真的很想大叫:不要鬧了!大家到底知不知道 Java 程式語言 / Bytecode / Virtual Machine / von Neumann 電腦架構 / 原生可執行檔 / 機器語語之間的關係,到底知不知道一個 Java 程式是怎麼執行的啊?!

請問你自己一個問題:Java 程式執行的時候,是執行你寫的原始碼嗎?

(答:不是,實際上執行的是 bytecode,也就是 javac 翻譯出來的 .class 檔)

請問你自己第二個問題:你在執行你的 Java 程式 (Bytecode) 的時候,你知道 Bytecode 到底是什麼東西,做什麼用的嗎?

(答:可以將 Bytecode 視為一套虛擬的『類機器語言』,將其交由 Virtual Machine 解譯後,可以產生電腦 CPU 真正能夠理解的機器語言指令)

請問你自己第三個問題:你知道什麼叫 von Neumann 架構嗎?你知道一個『原生電腦程式』是如何在 von Neumann 架構上執行的嗎?

(答:如果不知道,請參閱『小人電腦』,裡面是簡化版的說明,但基本上目前的所有電腦都不脫離這個架構。)

如果你看完了上面的小人電腦,請你問你自己第四個問題:你在小人電腦裡,有看到『資料結構』、『函數』、『物件』、『類別』這種東西嗎?

(答:沒有,只有記憶體/指令/暫存器/Program Counter ,而且記憶體位置好像也都知能存數字【註】)

(謎之音:那我的函數、物件、資料夾構在哪裡?提示:我們還有可執行檔以及 Bytecode 這兩個東西沒講到。)

【註】嚴格來說這並不正確

請再問你自己第五個問題:你知道我們剛剛『定義』出來的計算總合的函式,如果翻譯成小人電腦的機器語言,用上述網頁中的機器語言表示出來會長什麼樣子嗎?

(答:你會看到 sum(n-1) 被翻成一個 CALL 指令,其參數 XX 的部份會是此函數的開頭,而 return 會被翻成 RETURN ,如果你不知道這是什麼意思,請把小人電腦再認真看一次,並注意最後一個函數呼叫的例子)

以上,是回答為什麼函數可以呼叫自己。

接下來,請再問你自己:記憶體 / 指標 / 參考 / 物件之間,究竟是什麼關係,你知道第二個 Node 的 Java 程式碼,被載入到記憶體後,到底長什麼模樣嗎?

(答:指標和參考本質上都是相同的,都是『記憶體位置』)

這不就很明顯了嗎?private Node next,說的是『next 是一個記憶體位置,而且這個記憶體位置應該要指到一個長得像 Node 的物件』。

但 next 真的必需指到 Node 物件嗎?(提示:多型/強制轉型/執行期錯誤)他不過就是個數字,來表示記憶體位置,誰管他指到什麼地方啊!

所以說,這有什麼好訝異的?不過就是『在 Node 的這個物件裡,有一個欄位是一個數字,他指到某一個記憶體的位置,而這個記憶體位址上的內容,應該要是另一個Node 物件(但不必然是)。』

這很直覺,很正常啊!

最後,回到原 PO 的問題,如果再把原 PO 的問題更簡化,可以問另一個問題,那就是以下的 Java 程式碼是否合法,如果它合法的話,執行這段 Java 程式碼裡的 test() 到底會發生什麼事,記憶體裡產生了哪些變化?

class Hello
{
    public void test ()
    {
        Hello hello = new Hello();
    }
}

public class Test
{
    public static void main (String [] args)
    {
        Hello hello = new Hello ();
        hello.test ();
    }
}

接下來,你要問你自己,Java 裡 new 出來的物件,到底在是住在記憶體的哪裡?而指到各物件的參考又活在哪裡?是在 Stack 還是在 Heap ?指標裡存的又是什麼?

再來, hello.test() 這一行會翻譯成什麼小人電腦的組合語言?(提示:Method call 和 function call 本質上是一樣的東西)

如果,你能回答出以上的問題,基本上就不會有『為什麼函數沒有定義完還可以呼叫自己,為什麼物件可以 new 自己,為什麼明明就還沒定義完,Node 裡卻可以有 Node』 的這種疑問了。

謎之音:所以說,這篇『爪哇學校的危害』還是有他的道理在的啊!

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2009-12-05 (週六) 11:27:06

[Android] 實現 Functional 的 Cursor。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

退伍了,終於有比較多的時間可以東摸西搞了,想當然爾,現在的我想玩的,就是 Scala + Android 啦!

Android 確實是個很不錯的平台,可是也有他弱的地方,其中我最不喜歡的,就數 Content Provider 啦。

在 Content Provider 的架構下,的確可以達到不同應用程式間溝通的的做用,可是操作的階層實在是太低了,竟然直接動到資料庫的指標去了。

這對於已經漸漸習慣了 Functional 的我而言,真的是很痛苦啊,想到要在那邊 move 來 move 去,就一整個煩人。

幸好,這個時候,很強大的 trait 就登場啦,只要下面短短幾行的程式,馬上就可以把 Cursor 變成 Functional 啦!

class CursorSeq (cursor: Cursor) extends Seq[Cursor]
{
    require (cursor.moveToFirst)
 
    override val length = cursor.getCount
    override val elements = new Iterator[Cursor] {
        override def hasNext = !cursor.isLast &&
                               !cursor.isAfterLast &&
                               !cursor.isClosed
 
        override def next : Cursor = {
            cursor.moveToNext()
            cursor
        }
    }
 
    override def apply (n: Int) : Cursor = {
        cursor.moveToPosition (n)
        cursor
    }
}

然後,再配上一些 Wrapper class……

case class ReminderItem private (id: Int, title: String,
                                 description: String, triggerTime: Int,
                                 maidID : String)
{
    def uri : Uri = Uri.parse (Reminder.ContentUri + "/" + id)
}

object ReminderItem
{
    private def get (cursor: Cursor) : Option[ReminderItem] =
    {
        if (cursor == null) {
            return None
        }
 
        val colID = cursor.getColumnIndex (Reminder.ID)
        val colTitle = cursor.getColumnIndex (Reminder.Title)
        val colDescription = cursor.getColumnIndex (Reminder.Description)
        val colTriggerTime = cursor.getColumnIndex (Reminder.TriggerTime)
        val colMaidID = cursor.getColumnIndex (Reminder.MaidID)
 
        val id = cursor.getInt (colID)
        val title = cursor.getString (colTitle)
        val description = cursor.getString (colDescription)
        val triggerTime = cursor.getInt (colTriggerTime)
        val maidID = cursor.getString (colMaidID)
 
        Some (ReminderItem (id, title, description, triggerTime, maidID))
    }
 
    def getAll (context: Context) : Seq[Option[ReminderItem]] =
    {
        var result : Seq[Option[ReminderItem]] = Nil
        val cursor = new CursorSeq (context.getContentResolver.
                                     query (Reminder.ContentUri,
                                            null, null, null, null))
 
 
        for (row <- cursor) yield get (row)
    }
}

就可以寫出 Functional,並且超簡潔的程式啦!例如:

val item = ReminderItem.getAll.filter (_ != None)

// 取出標題是 Hello 的 ReminderItem
item.filter (i => i.title == "Hello")

// 取出所有 ReminderItem 的 Title
item.map ( i => i.title)

這樣比起直接下 Query 來得清楚多囉。

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2009-11-26 (週四) 10:26:29

角度。

此文評價一顆星二顆星三顆星四顆星五顆星
Loading ... Loading ...

看到咪遜喵的這一篇『第一次面試』的狀況,說實在,我也不知道在當下的話,我會選哪一個答案。

或許不是很適合,可是看到那篇文章的當下,我想到的是『明朝那些事』第七集裡的一句話--所謂歷史唯物主義的要點,就是所有的歷史事件,都要根據當時的歷史環境來考慮。

其實兩方面的想法我都能理解,咪遜把這次的面試當做考試,所以會做出這樣的選擇,或許我也會。

咪遜的理由他寫的很清楚了,我的理由也很簡單--不該是我的分數,我不會去拿。所以,我討厭做弊,我討厭抄襲,考試時間到了就停筆,拿我該拿的分數,當我該被當的科目,而不是去做弊,去求情。

咪遜想到的是主考官想要的是『如果你有一百分的實力,那你應該就要想辦法把一百分表現出來』。

不過當我看到後的第一個想法,是換個角度想想--如果這不是考試,而是客戶給你的案子呢?

你能夠對客戶說:『啊!時間到了,我東西就做到這裡,還有一些部份沒做好,不過我們還是來交貨吧!』

不行嘛!當然不行嘛!當然是要和客戶說抱歉,這東西只要再一點時間就可以完成,我會把他弄好的!

當然,這也只是我自己的猜測而已。

至於,如果當時的主角是我,我會做出什麼選擇呢?其實,我也不知道,真的不知道。XD

將本文加入 Hemidemi 書籤
Brian Hsu (墳墓)
2009-11-26 (週四) 10:09:37