2014/11/24

再用 list comprehension 解魔方陣

小朋友的學校又給了一個特殊的魔方陣題目,erlang 的 list comprehension 解魔方陣真的很快,把每一種可能發生的狀況慢慢列出來,就可以得出結果了。

題目:
五芒星共十個節點,填入 1 ~ 10 的數字,外側有五個小三角形,每一個小三角形的三個數字和,如果是 13,請列出十個節點的填法。再把總和改為14,15,16,17,18,19,20,分別找出可能的填寫方法。

如果用筆慢慢計算,必須要這樣想,五個三角形總和都是 13,13*5=45,因為 1~10 十個數字的總和是 55,但中間的 A,B,C,D,E 各被多加了一次,所以 A+B+C+D+E+55=45,因此 A+B+C+D+E = -10,此題無解。

至於 14,就是 A+B+C+D+E+55=14*5,所以 A+B+C+D+E=15,再慢慢列舉 A,B,C,D,E 進而找出 F,G,H,I,J。

用 erlang 可以這樣寫 matrix.erl

-module(matrix).
-export([resolve/1]).

resolve(SUM) ->
    statistics(runtime),
    statistics(wall_clock),
    K = lists:seq(1,10),
    L=[{A,B,C,D,E,F,G,H,I,J}||
     A <- K,
     B <- K--[A],
     F <- K--[A,B],
     A+B+F == SUM,

     C <- K--[A,B,F],
     G <- K--[A,B,F,C],
     B+C+G == SUM,

     D <- K--[A,B,F,C,G],
     H <- K--[A,B,F,C,G,D],
     C+D+H == SUM,

     E <- K--[A,B,F,C,G,D,H],
     I <- K--[A,B,F,C,G,D,H,E],
     D+E+I == SUM,

     J <- K--[A,B,F,C,G,D,H,E,I],
     A+E+J == SUM
    ],
    {_, Time1} = statistics(runtime),
    {_, Time2} = statistics(wall_clock),
    io:format("runtime=~p wall_clock=~p ~n", [Time1, Time2]),
    L.

編譯後,再到 erl 執行。

1> matrix:resolve(13).
runtime=0 wall_clock=0
[]
2> matrix:resolve(14).
runtime=15 wall_clock=15
[{1,3,5,2,4,10,6,7,8,9},
 {1,4,2,5,3,9,8,7,6,10},
 {2,4,1,3,5,8,9,10,6,7},
 {2,5,3,1,4,7,6,10,9,8},
 {3,1,4,2,5,10,9,8,7,6},
 {3,5,2,4,1,6,7,8,9,10},
 {4,1,3,5,2,9,10,6,7,8},
 {4,2,5,3,1,8,7,6,10,9},
 {5,2,4,1,3,7,8,9,10,6},
 {5,3,1,4,2,6,10,9,8,7}]

我們把一個答案填到五芒星中。

目前的缺點是 A,B,C,D,E 重複的 set,並沒有排除掉相同的答案,還沒想到要怎麼處理。

結果蠻奇怪的,13, 15, 18, 20 沒有解,14,16,17,19 有解,沒有什麼特別的規則。

2014/11/17

kamailio installation step by step

先前已經測試過 opensips,雖然可以使用,但還是遇到一些問題,例如網頁使用者界面的套件一直沒辦法運作地很順利,我們試著改成版本更新速度比較快的 kamailio,接下來就是安裝的過程。

準備工作

安裝 kamailio 之前,必須把 CentOS 基本的套件裝好,通常我們會把開發者工具、kernel 的開發套件都裝上去,還會裝上 EPEL、rpmforge 這兩個 package repository。

因為 kamailio 的 dialplan 需要,所以必須要安裝 pcre,因為 rtpengine 的需要,所以要安裝 xmlrpc-c-devel iptables-devel。

yum -y install pcre pcre-devel libpcap libpcap-devel libunistring libunistring-devel xmlrpc-c-devel iptables-devel

安裝 kamailio

kamailio 才剛在 2014/10/16 發布 kamailio 4.2.0 版,我們可以到 kamailio download page 下載 kamailio-4.2.0_src.tar.gz。

把原始程式碼放在 /usr/local/src 資料夾中。

cd /usr/local/src
tar zxvf kamailio-4.2.0_src.tar.gz

cd kamailio-4.2.0
make cfg

修改 modules.lst 一行資料

vi modules.lst

include_modules= db_mysql websocket tls dialplan

編譯並安裝 kamailio

make all
make install

現在安裝完成的 kamailio

設定檔在 /usr/local/etc/kamailio
執行擋在 /usr/local/sbin

這裡面有4個執行檔

kamailio - Kamailio SIP server
kamdbctl - script to create and manage the Databases
kamctl - script to manage and control Kamailio SIP server
sercmd - CLI - command line tool to interface with Kamailio SIP server

modules在 /usr/local/lib64/kamailio/modules/
文件在 /usr/local/share/doc/kamailio/
man page 在 /usr/local/share/man/man5/ 以及 /usr/local/share/man/man8/

產生 mysql database

編輯 /usr/local/etc/kamailio/kamctlrc

vi /usr/local/etc/kamailio/kamctlrc
把這一行設定的註解移掉
DBENGINE=MYSQL

修改 DB 預設的密碼

DBRWPW="dbpassword"
DBROPW="dbpassword"

執行

/usr/local/sbin/kamdbctl create

結果會產生兩個 mysql users,預設密碼的部份剛剛有改過了,應該會變成 dbpassword。

kamailio 預設密碼 kamailiorw
    有 'kamailio' database 完整權限
kamailioro 預設密碼 kamailioro
    有 'kamailio' database read-only 權限

雖然 已經改過 /usr/local/etc/kamailio/kamctlrc 的密碼,需要再一次覆寫資料庫的密碼。

mysql -u root -p
use mysql;
UPDATE user SET Password=PASSWORD("dbpassword") WHERE User='kamailio';
UPDATE user SET Password=PASSWORD("dbpassword") WHERE User='kamailioro';
flush privileges;

製作啟動服務的 script

先把 kamailio 核心的設定檔改好。

cp /usr/local/src/kamailio-4.2.0/pkg/kamailio/centos/6/kamailio.init /etc/init.d/kamailio
mkdir -p /etc/kamailio
cp /usr/local/etc/kamailio/kamailio.cfg /etc/kamailio/

修改 DB 的設定

vi /etc/kamailio/kamailio.cfg

在檔案前面增加三行
#!define WITH_MYSQL
#!define WITH_AUTH
#!define WITH_USRLOCDB
修改 DBURL 密碼
#!ifndef DBURL
#!define DBURL "mysql://kamailio:max168kit@localhost/kamailio"
#!endif

以 kamailio 的 init script sample 把啟動服務的 script 做好。

cp /usr/local/src/kamailio-4.2.0/pkg/kamailio/centos/6/kamailio.sysconfig /etc/sysconfig/kamailio

chmod 755 /etc/init.d/kamailio

修改 script 內容

vi /etc/init.d/kamailio
修改這2行
KAM=/usr/local/sbin/kamailio
RUN_KAMAILIO=yes


# 最後面增加 -f $KAMCFG 
OPTIONS="-P $PID_FILE -m $SHM_MEMORY -M $PKG_MEMORY -u $USER -g $GROUP $EXTRA_OPTIONS -f $KAMCFG "

修改執行 kamailio 的 user 權限

mkdir -p /var/run/kamailio
adduser --system --shell "/sbin/nologin" --home /var/run/kamailio kamailio
chown kamailio:kamailio /var/run/kamailio

設定 SIP Domain 變數, 有二種方式

1.
export SIP_DOMAIN=192.168.1.24
2.
vi /root/.kamctlrc
SIP_DOMAIN=192.168.1.24

執行 script 時有一些錯誤。

which: no greadlink in (/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/)

修改第 19 行可以解決這個問題

vi /usr/local/sbin/kamctl

which greadlink > /dev/null 2>&1

使用獨立的log檔案

檢查 kamailio.cfg 設定

vi /etc/kamailio/kamailio.cfg
debug=3 #此值控制日誌輸出的詳細程度,3為普通,4為詳細(會產生很多日誌)。
log_stderror=no #設置為no表示將日誌輸出到文件,否則輸出到控制台(應該是以前台方式啟動opensips服務時才有用)。
log_facility=LOG_LOCAL0 #應該是用於在syslog服務的配置文件裡區分opensips產生的日誌(見下面"使用獨立的log文件")。
fork=yes #設置為yes表示在後台啟動opensips服務,設置為no表示在前台啟動。

kamailio 使用syslog服務,在沒有作任何設定的狀況下,log 會進入/var/log/message這個檔案,如果希望使用獨立的log檔案,可以這樣設定

touch /var/log/kamailio.log
vi /etc/rsyslog.conf
增加一行
local0.* /var/log/kamailio.log

/etc/init.d/rsyslog restart

一併把 logrotate 設定好

vi /etc/logrotate.d/kamailio.logrotate
/var/log/kamailio.log {
   missingok
   rotate 5
   daily
   create 0640 root root
}

安裝 網頁界面 siremis

把 siremis 準備好

cd /usr/local/src
tar zxvf siremis-4.1.0.tgz
mv siremis-4.1.0 /var/www/html/siremis

產生 apache conf file

cd /var/www/html/siremis
make apache-conf

依照內容建議,編寫 apache httpd config for siremis

vi /etc/httpd/conf.d/siremis.conf
Alias /siremis "/var/www/html/siremis/siremis"
<Directory "/var/www/html/siremis/siremis">
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    Allow from all
    <FilesMatch "\.xml$">
        Order deny,allow
        Deny from all
    </FilesMatch>
    <FilesMatch "\.inc$">
        Order deny,allow
        Deny from all
    </FilesMatch>
</Directory>

修改資料夾權限,重新啟動 httpd

make prepare
chown -R apache:apache /var/www/html/siremis

service httpd restart

建立 siremis DB

mysqladmin create siremis -p

mysql -uroot -p
GRANT ALL PRIVILEGES ON siremis.* TO siremis@localhost IDENTIFIED BY 'dbpassword';

連上 siremis 網頁,一開始會是一個設定的 wizard,我們必須把資料庫的密碼,改成剛剛修改後的 dbpassword。

http://localhost/siremis/

在產生 DB 的畫面最下面,把這三個項目打勾

Import Default Data 打勾
Update sip database 打勾
Replace DB Config 打勾

最後只要看到 Installation Completed 的畫面,就完成安裝程序了。

2014/11/12

Test Simple SIP application with Mobicents and Eclipse

看了幾篇,主要介绍如何使用Mobicents、Tomcat和eclipse創建Sip Servlet應用的方式。參考Developping a Simple SIP application with Mobicents and Eclipse的步驟,很快就能將測試環境安裝好。

目前手邊的程式在jboss下執行是沒問題的,但如果能在這樣的eclipse&tomcat的環境下開發測試,應該會省下一些麻煩的步驟,因此花了些時間測試一下。

我使用的測試環境是
作業系統:Window7 32位元
Eclipse Java EE IDE for Web Developers:Luna Service Release 1 (4.4.1) (download url http://www.eclipse.org/downloads/ )
Mobicents Sip Servlets:mss-3.0.564-apache-tomcat-7.0.50.zip ( download url https://github.com/Mobicents/sip-servlets/releases )

環境安裝請參考Developping a Simple SIP application with Mobicents and Eclipse ,這裡不再贅述。

Creating a new simple project in Eclipse

  • Click File / New / Dynamic Web Project
We will generate a simple SIP application without any Web support (only basic JSP).
  • In the dialog
  1. enter "SimpleServlet" as project name.
  2. select 2.4 as module version (2.5 version does not allow simple SIP app)
  3. Select "Converged SIP / Web" as configuration
如果順利安裝完Mobicents plugin 以上步驟是沒有問題的。

以下這個步驟非常重要,否則server是跑不起來的...
  • Open the file Servers / Mobicents at localhost-config / server.xml
  • At the end of the file, locate the Context definition :
<context docbase="SimpleServlet" path="/SimpleServlet" reloadable="true" source="org.eclipse.jst.j2ee.server:SimpleServlet"></host>

  • Remove the Context tag. KEEP </host> the tag only !!
Guru's help needed : if you do not remove the context definition from server.xml, Sip servlets are not started at launch time.......

在SimpleSipServlet.java 加入測試 code 後就就可以deploy,但檢查的步驟證明我的範例程式出了問題。

  • Deploy your application : in the servers pane, right clic on your server, then deploy.
  • Now start the server. On the servers pane, right click on your server, then choose Deploy, and Start
  • Check the log file ("console" view). Check that you do not have any error (no excpetion).
  • You should see the log ****** the simple sip servlet has been started *********
  • Now, the server should be listening on the UDP port 5080 (can be checked by the netstat -an command). It should contain the following line. (if you want grep, I recommand you install MINGW to get grep and tail).

UDP 127.0.0.1:5080 *:*    ----> 找不到 冏

---------------
紅字的部份就是檢查失敗的地方,查找了sip servlet相關文章,仍然搞不清楚問題是出在哪?
解決方法待續...



android app sending simple data to other app

在Project appA的MainActivity.java onCreate Method 中為按鈕填入事件

Button btnCall = (Button) findViewById(R.id.btnCall);
btnCall.setOnClickListener(new OnClickListener(){

@Override
public void onClick(View v) {

    Intent intent = new Intent(Intent.ACTION_MAIN);
    intent.setComponent(new ComponentName("com.example.appB", "com.example.topdf.MainActivity"));
    startActivity(intent);
}
=======
建立 Project appB
將appA 及 appB 程式deploy到測試裝置或模擬器試試,執行appA,按下按鈕就會叫用appB 但如果沒有安裝appB則會異常結束。
另一種方式是如果只知道package而不知用哪個Activity, 則可以用這個寫法,但如果沒有安裝appB仍然會異常結束。
Intent LaunchIntent = getPackageManager().getLaunchIntentForPackage("com.example.appB");
startActivity(LaunchIntent);
也可以用傳送資料的方式叫用其他的app
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "Hello I'm appA.");
sendIntent.setType("text/plain");
startActivity(sendIntent);
這樣即使沒有安裝appB,也可以選擇叫用其他app


2014/11/10

在 CentOS 6 安裝 redmine 專案管理工具

原本公司內是使用 bugzilla 做專案的 task/issue tracking 工具,但是 bugzilla 卻沒有 gantt chart 的功能。我們需要的是一個可以取代 bugzilla 的專案管理工具,這要分兩個部份來看,programmer 必須要能使用 Eclipse Mylyn 直接連結、查看、處理跟自己相關的 task/issue,專案管理者要能看到 gantt chart,再加上一些其他的管理工具,survey 後就選擇跟 trac 相近的 redmine。

雖然 redmine 在安裝上並不像 php 專案那麼簡單,但因為內建提供了 gantt chart,網頁使用者界面又比較簡潔,因此就試著安裝 redmine,接下來進行試用,最後就是把 bugzilla 退役。

準備工作

安裝 redmine 之前,必須把 CentOS 基本的套件裝好,通常我們會把開發者工具、kernel 的開發套件都裝上去,還會裝上 EPEL、rpmforge 這兩個 package repository。

redmine 的官方網頁目前提供了兩個版本 2.5.3 以及 2.6.0,我先把兩個版本的原始程式碼取回來。

安裝 redmine

首先把 Ruby on Rails 相關的套件裝好。

yum -y install ruby ruby-devel ImageMagick ImageMagick-devel rubygem-rake

wget http://download.opensuse.org/repositories/home:csbuild:centosextra/CentOS_CentOS-6/home:csbuild:centosextra.repo
mv home:csbuild:centosextra.repo /etc/yum.repos.d/

yum -y install rubygem-bundler

在 MariaDB(或是 MySQL)建立 redmine DB 跟使用者資料。

mysql --user=root --password=dbpassword
create database redmine character set utf8;
create user 'redmine'@'localhost' identified by 'dbpassword';
grant all privileges on redmine.* to 'redmine'@'localhost';
quit;

解壓縮 redmine 原始程式碼,一開始我們是先試著安裝 2.6.0 版,但因為到後面一直遇到 email 的設定問題,嘗試多個解決方案的過程中,又使用 2.5.3 版重新安裝了一次,基本上 2.6.0 以及 2.5.3 的安裝過程沒有什麼很大的差異。

tar zxvf redmine-2.6.0.tar.gz
mv redmine-2.6.0 /var/www/redmine

cd /var/www/redmine/config
cp database.yml.example database.yml

設定資料庫使用者名稱、密碼。

vi database.yml

把
production:
  adapter: mysql2
  database: redmine
  host: localhost
  username: root
  password: ""
  encoding: utf8

改為

production:
  adapter: mysql2
  database: redmine
  host: localhost
  username: redmine
  password: "dbpassword"
  encoding: utf8

調整 Gemfile 的相依性套件,增加 mongrel, dispatcher

vi /var/www/redmine/Gemfile

在
gem "rbpdf", "~> 1.18.1"
這一行的後面增加兩行

gem 'mongrel', '>= 1.2.0.pre2'
gem 'dispatcher'

安裝 ROR 相關套件,在鍵入 bundle install 這一個步驟的指令之後,console 的畫面會停住,看起來很像是當掉的狀態,而且會停著大約10~20分鐘,搜尋解法後,大家回應只說這可能是 https://rubygems.org/ 的網路問題,所以這個步驟就只能等,沒有別的解決方法。

cd /var/www/redmine
bundle install --without development test

安裝 redmine 資料庫

rake generate_secret_token
RAILS_ENV=production rake db:migrate
# 載入預設資料
RAILS_ENV=production rake redmine:load_default_data

# 輸入 zh-TW

如果先裝了 2.6.0 再重裝 2.5.3,在上面這個步驟,就會遇到 rake 升級後的 版本錯誤,這時候就改用下面這些指令

bundle exec rake generate_secret_token
RAILS_ENV=production bundle exec rake db:migrate
RAILS_ENV=production bundle exec rake redmine:load_default_data

調整 redmine 資料夾

mkdir -p tmp tmp/pdf public/plugin_assets
chown -R apache:apache files log tmp public/plugin_assets
chown -R apache:apache /var/www/redmine
chmod -R 755 files log tmp public/plugin_assets

基本上這樣就裝好了,可以直接用 webrick 啟動 redmine。

ruby script/rails server webrick -e production

redmine 網頁會在

http://localhost:3000/

跟 Apache 整合

因為要用Apache當作我的Web Server,所以要透過 Phusion passenger 做和 Ruby on Rail的處理。

首先要安裝Phusion passenger

yum -y install make zlib-devel ruby-devel rubygems ruby-libs apr-devel apr-util-devel httpd-devel mod_dav_svn subversion subversion-ruby automake autoconf curl-devel darcs hg bzr

cd /var/www/redmine/
gem install passenger
passenger-install-apache2-module

畫面看起來很奇怪時, 就鍵入 ! ,因為預設就選擇了 ruby python, 所以就直接 enter。

在/etc/httpd/conf.d/加上redmine.conf,並編輯redmine.conf加入以下的設定。

vi /etc/httpd/conf.d/redmine.conf

LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-4.0.53/buildout/apache2/mod_passenger.so
<IfModule mod_passenger.c>
    PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-4.0.53
    PassengerDefaultRuby /usr/bin/ruby
</IfModule>

RailsBaseURI /redmine
<Directory /var/www/redmine/public>
    # This relaxes Apache security settings.
    AllowOverride all
    Options -MultiViews
</Directory>

在DocumentRoot的路徑下加上redmine的symbolic link

ln -s /var/www/redmine/public /var/www/html/redmine

這個設定過程很重要,因為 redmine 官方只提供了以 virtual host 的方式跟 apache 整合,但我們希望用 http://localhost/redmine/ 的網址方式,連結 redmine,努力兩天後,得到上面的解決方案。

設定 email

首先取得設定檔

cd /var/www/redmine/config
cp configuration.yml.example configuration.yml

因為我們是使用 gmail 帳號,同時也使用了 google apps 代管公司的 email,在設定 email 的時候,會遇到很多狀況,基本上再努力兩天,得到下面的解決方案。

修改 Gemfile.lock 的 mail 版本號碼, 由 2.5.4 改為 2.5.3

vi /var/www/redmine/Gemfile.lock

修改第 7 行
    mail (~> 2.5.3)

修改第 50 行
    mail (2.5.3)

修改後要執行

bundle install --without development test

如果是使用 gmail 帳號,要把 POP3 的功能打開,然後填寫設定

vi /var/www/redmine/config/configuration.yml
    delivery_method: :smtp
    smtp_settings:
       enable_starttls_auto: true
       address: "smtp.gmail.com"
       port: 587
       domain: "smtp.gmail.com"
       authentication: :login
       user_name: "maxkit@gmail.com"
       password: "youremailpassword"

如果是使用 google apps 代管 email 帳號,要把 POP3 的功能打開,然後填寫設定

vi /var/www/redmine/config/configuration.yml
  email_delivery:
    delivery_method: :smtp
    smtp_settings:
      enable_starttls_auto: true
      address: "smtp.gmail.com"
      port: 587
      domain: "maxkit.com.tw"
      authentication: :login
      user_name: "maxkit@maxkit.com.tw"
      password: "youremailpassword"

讓 eclipse mylyn 可以使用 redmine

Redmine官網的HowToMylyn裡提到Redmine的V2.x以後需要使用Redmine-Mylyn Connector。但是這個只是服務端的plugin。客戶端的Eclipse plugin有好幾個git repo的clone。目前最活躍的是這兩個。

服務端的Redmine plugin的版本庫地址: https://github.com/danmunn/redmine_mylyn_connector

客戶端的Eclipse plugin的版本庫地址: https://github.com/ljader/redmine-mylyn-plugin

安裝過程如下

cd /var/www/redmine/plugins
git clone git://github.com/danmunn/redmine_mylyn_connector.git
cd ..
bundle install --without development test

Eclipse 的部份則是先下載 plugin: net.sf.redmine_mylyn.p2repository-0.4.0-SNAPSHOT.zip

接下來在 Eclipse -> Help -> Add New Software -> Add -> Archive ,選取 net.sf.redmine_mylyn.p2repository-0.4.0-SNAPSHOT.zip 後就可以安裝 plugin,剩下的部份,就跟一般設定 mylyn repository 一樣了。

結語

雖然 redmine 的界面簡潔,但要安裝 remine 會遇到不少困難,而這些問題,在官方網站卻找不到明確的幫助,反而得要自己慢慢地 google 搜尋,然後測試每一個人說的到底對或錯,才能解決這些奇怪的問題。

2014/11/03

scala: 不在迴圈中使用變數

通常在撰寫迴圈時,最直覺的寫法,就是帶入變數,隨時在迴圈中檢查變數的值,也可以利用變數控制,是否要跳出迴圈,但 functional style programming 的重點,除了希望 programmer 能用更易讀的方式撰寫程式,同時希望程式的效能,可以得到顯著的提昇,因此消滅不必要的迴圈與變數,成了 functional programming 的另一項重要任務,而 java programmer 要學習的,是忘記那些 OO Design Patterns,讓程式碼更精簡。

Java 的定位是商用語言,以往的歷史也證實,在 Server Side 的運算環境中,採用 Java J2EE solution 是個很好的選擇,但也因為是 Server Side 的語言,java programmer 比較不在意使用了多少變數,消耗了多少記憶體,著眼點通常放在要把功能做好,模組切割要合理,系統要穩定,只要選個好一些 Server 多一些的記體體就可以運作了。

Function Programming 讓我重新回到撰寫 C 語言程式碼的心情,C 語言追求卓越的速度,在意自己使用了多少記憶體,有時候甚至還要動用 assembly,相容於 Java 的 Scala 是個雙面人,我們該沿用 Java 帶來的物件導向分析習慣,將系統模組化,接下來在實作時,思考如何撰寫高效率的 FP 程式。

if expression

  var filename = "default.txt"
  if (!args.isEmpty)
    filename = args(0)

如果換個寫法,就可以不需要使用 var,程式也比較短,使用 val 也比較接近 functional style,要使用 variable 之前,要先想一下是不是可以 改寫成 expression,另外要盡可能使用 val,可讓程式更容易 refactor。

  val filename =
    if (!args.isEmpty) args(0)
    else "default.txt"

while loops

unit value 寫成 (),() 的存在,就是跟 java void 不同的地方,因為 greet() 會回傳 Unit, 所以 greet() 就會等於 ()。

  scala> def greet() { println("hi") }
  greet: ()Unit

  scala> greet() == ()
  hi
  res0: Boolean = true

以 java 的習慣,可能會這樣寫

  var line = ""
  while ((line = readLine()) != "")
    println("Read: "+ line)

但因為 line = readLine() 的結果是 () Unit,而 Unit 一定不會等於 "",所以就會永遠是 true

  var line = ""
  do {
    line = readLine()
    println("Read: "+ line)
  } while (line != "")

使用 recursion 取代 while

如果要計算 g.c.d 可以這樣寫

  def gcdLoop(x: Long, y: Long): Long = {
    var a = x
    var b = y
    while (a != 0) {
      val temp = a
      a = b % a
      b = temp
    }
    b
  }

改用 functional style: recursion 撰寫 gcd, 同時可以省去很多不需要的vars。

  def gcd(x: Long, y: Long): Long =
    if (y == 0) x else gcd(y, x % y)

generator

  val filesHere = (new java.io.File(".")).listFiles

  for (file <- filesHere)
    println(file)

file <- filesHere 的語法稱為 generator

如果要對一堆檔案重複進行某個運算,也許我們會很直覺地這樣寫

  for (i <- 0 to filesHere.length - 1)
    println(filesHere(i))

這種寫法的缺點是需要產生一個變數 i,因此這種寫法,在 scala 是不常見的,我們必須修改成,直接對 file collection 做 iteration,而 iteration 可能有下面四種問題:filtering、nested iteration、mid-stream variable binding、producing a new collection。

filtering

取得檔名是 .scala 結尾的 檔案

  val filesHere = (new java.io.File(".")).listFiles
  for (file <- filesHere if file.getName.endsWith(".scala"))
    println(file)

也可以使用兩個以上的 filter

  for (
    file <- filesHere
    if file.isFile
    if file.getName.endsWith(".scala")
  ) println(file)

nested iteration

在 for 裡面,也可以同時使用多個 generator

  def fileLines(file: java.io.File) = 
    scala.io.Source.fromFile(file).getLines().toList

  def grep(pattern: String) =
    for (
      file <- filesHere
      if file.getName.endsWith(".scala");
      line <- fileLines(file)
      if line.trim.matches(pattern) 
    ) println(file +": "+ line.trim)

  grep(".*gcd.*")

mid-stream variable binding

上面的程式碼,重複執行了 line.trim,如果想要只運算一次,就要產生一個變數 trimmed = line.trim,trimmed 用了兩次, 一次在 if, 一次在 println。

  def grep(pattern: String) =
    for {
      file <- filesHere
      if file.getName.endsWith(".scala")
      line <- fileLines(file)
      trimmed = line.trim
      if trimmed.matches(pattern)  
    } println(file +": "+ trimmed)

  grep(".*gcd.*")

producing a new collection

用 yield, 可以把過濾後的結果 array 紀錄起來,結果會得到 Array[File], 因為 filesHere 是 array, file 是 File。yield 必須放在 for 的外面,語法為 for clauses yield body。

  def scalaFiles =
    for {
      file <- filesHere
      if file.getName.endsWith(".scala")
    } yield file

try - catch - finally

在 finally 中關閉 file, 確保檔案一定有被關閉,scala 可以在 finally 裡面 return value, 但會把結果覆蓋掉。

  val file = new FileReader("input.txt")
  try {
    // Use the file
  } finally {
    file.close()
  }

match expression

類似 java 的 switch,預設是 _ ,每個 case 裡面不需要寫 break,不只可以用 int, enum, 也可以用 string,可直接將 match 結果,儲存到變數中。

  val firstArg = if (!args.isEmpty) args(0) else ""

  val friend =
    firstArg match {
      case "salt" => "pepper"
      case "chips" => "salsa"
      case "eggs" => "bacon"
      case _ => "huh?"
    }

  println(friend)

no break and continue

因為 scala 沒有 break 跟 continue,最簡單的方式,就是把 continue 換成 if,把 break 換成 boolean 變數。

  var i = 0
  var foundIt = false

  while (i < args.length && !foundIt) {
    if (!args(i).startsWith("-")) {
      if (args(i).endsWith(".scala"))
        foundIt = true
    }
    i = i + 1
  }

更好的寫法是 recursive 呼叫 seachFrom

  def searchFrom(i: Int): Int =
    if (i >= args.length) -1
    else if (args(i).startsWith("-")) searchFrom(i + 1) 
    else if (args(i).endsWith(".scala")) i
    else searchFrom(i + 1)

  val i = searchFrom(0)

Variable scope

scala 的 scoping rule 跟 java 大部分都一樣
只有一點不同,scala 可在 nested scopes 中定義同樣的 variable name。

  val a = 1;
  {
    val a = 2 // Compiles just fine
    println(a)
  }
  println(a)

但是在 scala interpreter 中,看起來雖然像是同一個 scope,但實際上是不同的,因此在 interpreter 中,變數可重複定義。

  scala> val a = 1
  a: Int = 1

  scala> val a = 2
  a: Int = 2

  scala> println(a)
  2

Reference

Programming In Scala by Martin Odersky, Lex Spoon, and Bill Venners