0%

在Imperative programming下待久了,對於Functional language的一切都是覺得很新鮮。
Learn You a Haskell for Great Good!看到中途,筆記一下一些新觀念。

List comprehension

跟數學裡面Set comprehension的表達方法很像,假設我們想要找出直角三角形中,周長小於50的三元組的數量。 (假設 a <= b <= c 且 a ^ 2 + b ^ 2 = c ^ 2)。

如果是以往的作法,大概會像這樣

1
2
3
4
5
6
7
8
9
for (int a = 1; a <= 10; a++)
#define sqr(x) (x * x)
typedef tuple<int, int, int> tuple3;
std::vector<tuple3> ans;
for (int a = 1; a <= 50; a++)
for (int b = a; b <= 50; b++)
for (int c = b; c <= 50; c++)
if ((sqr(a) + sqr(b) == sqr(c)) && ((a + b + c) < 50))
ans.push_back(make_tuple(a, b, c));

如果是Haskell會是這樣,不同的語言寫法會不一樣,不過這上面列的C++11實作方式還不如上面來的直覺。

1
let xs = [(a, b, c) | a <- [1..50], b <- [a..50], c <- [b..50], a ^ 2 + b ^ 2 == c ^ 2, a + b + c < 50]

Pattern Matching

這邊跟我們常知的Pattern Matching不太相同,

在C99之後,新增了一種初始化方法。可以方便的設定structure/arry的初始值。帶來了極大的靈活

在之前的時期,要初始化一個structure,只能用以下的方式。

lang: c
1
2
3
4
5
typedef struct MyData {
char *p;
int v;
} MyData;
MyData a = { "name" , 10};

這種方式稱作Aggregate Initialization
需要按照Type定義的方式來初始化,一旦我們修改MyData資料結構,所有用到MyData的初始化都需修改。
在C99之後,我們可以這樣做

lang: c
1
MyData a = { .v = 10, .p = "name"};

這樣就可以用任何順序指定初始值了。
另一個常用的應用場合是Array,拿以下這段程式碼當範例。

lang: c
1
2
3
4
5
6
7
8
9
10
enum {
YAHOO = 0,
GOOGLE,
FACEBOOK
};
char *old_style_weburl[] = {
"www.yahoo.com",
"www.google.com",
"www.facebook.com"
};

當我們將 FACEBOOK跟YAHOO的順序對換之後,old_style_weburl的對應關係也要跟著改變。

lang: c
1
2
3
4
5
char *new_style_weburl[] = {
[FACEBOOK] = "www.facebook.com",
[YAHOO] = "www.yahoo.com",
[GOOGLE] = "www.google.com"
};

這樣子不管enum裡的順序怎麼改變,new_style_weburl對應關係都能維持正確。
有興趣的可以參考Designated Initializers學到更多。

趁著Android 4.3的出現,記錄一下如何編譯一個Android環境。

首先,建立一個Android目錄。

1
2
3
4
5
$ mkdir Android && cd Android
$ mkdir bin && cd bin
$ curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > /repo
$ chmod a+x repo
$ cd ..

建立一個source的目錄存放程式碼。

1
2
$ mkdir source && cd source 
$ ../bin/repo init -u https://android.googlesource.com/platform/manifest -bandroid-4.3_r2.1

開始下載,時間很久,請耐心等待

1
$ ../bin/repo sync

建置環境

1
2
3
$ source build/envsetup.sh
$ lunch full-eng
$ make -j 4

完成之後執行開啟模擬器

1
$ emulator

如果又更多問題,可以參考官網的說明,建議在64 bits的Linux底下操作。

接續上個範例

我們將add2這個函數從demo中題取出來,建立一個math library,讓其他人使用。
提供header file跟library,header file放在include目錄底下,source code放在lib底下>
我們的header file: MyMath.h

1
2
3
4
#ifndef _MyMath_H_
#define _MyMath_H_
int add2(int, int);
#endif

實作add2.c

1
2
#include "MyMath.h"
int add2(int a, int b) { return a + b; }

重點的CMakeLists.txt部分

1
ADD_LIBRARY(mymath STATIC add2.c)

重新改寫我們的demo

1
2
3
4
5
6
7
#include <stdio.h>
#include "MyMath.h"
int main()
{
printf("%d\n", add2(1, 1));
return 0;
}

以及CMakeLists.txt

1
2
ADD_EXECUTABLE(demo demo.c)
TARGET_LINK_LIBRARIES(demo mymath)

目錄下的CMakeLists.txt也要跟著更新

1
2
3
4
5
6
PROJECT(CMakeDemo C)
cmake_minimum_required(VERSION 2.8)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include)
SUBDIRS(src lib)

在城市規模小的時候,直接寫Makefile是個比較快的解決方案,不過當規模更大,以及要跨平台的時候,CMake的優勢就出現了。

如何使用編譯參數

上面這個範例,我們是使用static library,我們假設要在編譯的時候選擇是要用static library或是shared library,該怎麼進行。

重新改寫 lib/CMakeLists.txt

1
2
3
4
5
if (ENABLE_SHAREDLIB)
ADD_LIBRARY(mymath SHARED add2.c)
else()
ADD_LIBRARY(mymath STATIC add2.c)
endif()

之後我們在產生Makefile之前下以下參數就能選擇了

1
$ cmake .. -DENABLE_SHAREDLIB=TRUE

這樣就能選擇編譯成shared librarry了。

從最簡單的範例開始

我們現在有個demo.c,裡面的程式碼如下。

demo.c lang: c
1
2
3
4
5
6
7
#include <stdio.h>
int add2(int a, int b) { return a + b; }
int main()
{
printf("%d\n", add2(1, 1));
return 0;
}

所需要的CMakeLists.txt內容也很簡單。

1
2
3
PROJECT(CMakeDemo C)
cmake_minimum_required(VERSION 2.8)
ADD_EXECUTABLE(demo demo.c)

這樣還不如直接用gcc編譯來的快很多,不過事情總會越來越複雜。

向工業標準邁進一步

我們把執行檔放在bin,而把程式碼放到src下。
根目錄的CMakeLists.txt改成這樣。

1
2
3
4
PROJECT(CMakeDemo C)
cmake_minimum_required(VERSION 2.8)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
ADD_SUBDIRECTORY(src)

因為新增了最後一杭,所以在src底也要新增一個CMakeLists.txt。

1
ADD_EXECUTABLE(demo demo.c

這樣我們就能在bin底下看到demo了。

最近看了Mixin之後,抒發一下自己的感想。
學過Software Engineering的,都知道DRY。如何共用程式碼就變成一門學問了。

假設我們現在有IA, IB, IC三個interface,然後有三個Concrete Class CA, CB, CC實作這三個介面
然後D, E兩個Class需要同時支援這三個interface,該怎麼做。

在C++這種支援Multiple Inheritance特性的語言,大概會是這個樣子。

lang: cpp
1
2
class D : public CA, CB, CC {};
class E : public CA, CB, CC {};

如果CA, CB, CC的內容風馬牛不相及,這個解決方案不錯,不然的話,Diamond Problem是個很頭大的問題。

而Java等語言等只支持單一繼承,因此避掉了Diamond Problem,不過卻引來其他問題。
有兩種常見的問題,第一種是實作介面。

1
2
class D implements IA, IB, IC {};
class E implements IA, IB, IC {};

假設某個Class的實作方式需要修改,需要修改,那麼D跟E的內容都需要修改。如此一來就達不到DRY的精神了。
另一種是亂七八糟的繼承方式。

1
2
3
4
5
6
7
class PD1 extends CA {};												class PE1 extends CA {};
class PD2 extends CB {}; class PE2 extends CB {};
class PD3 extends CC {}; class PE3 extends CC {};
class PD4 extends PD1 {}; class PE4 extends PE1 {};
class PD5 extends PD2 {}; class PE5 extends PE2 {};
class PD6 extends PD3 {}; class PE6 extends PE3 {};
class D extends PD6 {}; class E extends Pe6 {}

雖然避掉的第一個問題,不過那眼花撩亂的繼承關係更麻煩了。

至於動態語言大行其道之後,Mixin提供另外一種思考模式。由於Duck Typing的支持,類與類之間沒有強烈的interface contract關係。以Ruby來說

lang: ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module IA 
end
module IB
end
module IC
end
class D
include IA
include IB
include IC
end
class E
include IA
include IB
include IC
end

mixin是一群method的集合,只要外在的class有滿足條件(Duck Typing),這個方案可以滿足大部分的需求。

StackOverFlow看到的一個範例。

lang: cpp
1
2
3
4
5
6
7
8
9
struct Functor {
template <typename T>
void function() {}
template <>
void function<int>() {}
};
Functor functor;
functor.function<char>();
functor.function<int>();

同樣的code,在gcc和clang編譯失敗,不過VC可以,同樣根據StackOvewflow的說法,這是VC的一個非標準Extension。

要模擬這個方案,可以靠function overloading來做。

lang: cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename T>
struct DummyIdentity {
typedef T type;
};
struct Functor {
template <typename T>
void function() {
function(DummyIdentity<T>());
}
private:
template <typename T>
void function(DummyIdentity<T>) {}
void function(DummyIdentity<int>) {}
};

找了一下在Linux底下寫OpenGL程式的方法,覺得有必要繞過glut,直接跟XWindow打交道,才能拿到更大的控制權。
終於找到一個可以用的骨架。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <X11/Xlib.h>
#include <iostream>

int main()
{
Display* dpy;

dpy = XOpenDisplay(NULL);

std::cout << std::endl;

if (dpy == NULL)
{
std::cout << "Error: could not open display";
return 0;
}
else
{
std::cout << "Success!";
}

int blackColor = BlackPixel(dpy, DefaultScreen(dpy));
int whiteColor = WhitePixel(dpy, DefaultScreen(dpy));

Window w = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0,
200, 100, 0, whiteColor, blackColor);

XSelectInput(dpy, w, StructureNotifyMask);

XMapWindow(dpy, w);

GC gc = XCreateGC(dpy, w, 0, NULL);

XSetForeground(dpy, gc, whiteColor);

while(true)
{
XEvent e;
XNextEvent(dpy, &e);
if (e.type == MapNotify)
{
break;
}
}

XDrawLine(dpy, w, gc, 10, 60, 10, 0);
XFlush(dpy);

while(true)
{
XEvent e;
XNextEvent(dpy, &e);
if (e.type == DestroyNotify)
{
break;
}
}

std::cout << std::endl << std::endl;
return 0;
}

需要連結X11 library才能順利成功

1
$ g++ demo.cpp -o demo -lX11

不過奇怪的是當關閉視窗時,總會有以下的錯誤訊息出現

XIO: fatal IO error 11 (Resource temporarily unavailable) on X server “:0.0”
after 12 requests (10 known processed) with 0 events remaining.
Success

Google了一下也沒什麼正確的解法,就不管他。
程式的骨架跟Win32的架構很像,因此不會太難了解。

在CodeProject看到這篇文章之後,參考其他文章而發表的。

GCC nested function

這個Extension只支援GNU C,Clang或者VC++都不行。
這種技術有其名稱,叫做trampoline

這個Hack可模擬Javasciprt等語言使用Closure的特性

lang: c
1
2
3
4
5
6
7
8
typedef int (*func_t)(int);
static func_t f(int arg) {
int nested(int nested_arg) {
return (arg + nested_arg);
}
return &nested;
}

原文提供了更變態的使用方式,不過很難閱讀。

lang: c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, char *argv[]) {
void (*MySub1)() = ({void _() {
printf("Hello ");
} (void (*)())_;});

MySub1();

({void _() {
printf("World!\n");
} (void (*)())_;})();

return 0;
}

原理