Wednesday, October 27, 2010

ติดใจ repl ของ Clojure

ช่วงนี้ research หาลู่ทางรับงานจากอเมริกา
โจทย์ก็คือไป crawl ดึงข้อมูล จาก medicare.gov
ความยากอยู่ตรง site นั้นใช้ .net เขียนแบบ component based (stateful)
การจะได้ข้อมูลมา ต้องฝ่าด่านตอบคำถาม จำนวน 4-5 หน้าเข้าไปก่อน
ก็เลยต้องใช้ browser จริงๆเข้าไปดึงข้อมูล

หวยก็เลยออกมาที่ Selenium + WebDriver
แต่ครั้นจะเขียนด้วย java ก็ไม่สนุก
เขียนด้วย ruby/python ก็เคยเขียนแล้ว
ก็เลยเลือกใช้ Clojure

Lisp มันมีข้อดีอย่างหนึ่งก็คือ
มันมี REPL ทำให้เราสามารถเขียนโปรแกรมในลักษณะ incremental ได้
เขียนไป ทดลองไป ปรับแต่งไป

สภาพแวดล้อมที่เลือกใช้พัฒนาก็คือ
emacs + cake

ลองดูตัวอย่าง ตอนทดสอบได้ใน video นี้
(ดูช่วงต้นๆก็พอ, net/web มันช้า หลังๆหมดไปกับการรอมันโหลด
ใครว่า web ราชการไทยช้า web ราชการฝรั่งก็ช้าเหมือนกัน)

video

Updated: video upload ผ่านทาง blogger ได้ความละเอียดต่ำเหลือเกิน
ดูรูปประกอบแล้วกัน



ตัวอย่าง code ที่ใช้

(defn send-key [by txt]
(let [elm (.findElement *driver* by)]
(.sendKeys elm (into-array [txt]))))

(defn click [by]
(let [elm (.findElement *driver* by)]
(.click elm)))

(defn init-queue []
(def *queue* (LinkedBlockingQueue.)))

(defn start-thread [driver func]
(.start
(Thread.
(fn []
(loop []
(let [code (.take *queue*)]
(binding [*driver* driver]
(func code)))
(recur))))))

(defn fetch [code]
(go "https://www.medicare.gov/find-a-plan/questions/home.aspx")
(send-key (by-css-selector "div#zip-code-field > input") code)
(click (by-css-selector "input[type=submit][alternatetext=\"Find Plans\"]")))

Related link from Roti

Tuesday, October 26, 2010

เรียนรู้วิธีที่ ExtJS format date

ใน ExtJS เราใช้ function format ในการกำหนดรูปแบบการแสดงผลของ Date object
ลองดูตัวอย่าง

new Date().format("c")
"2010-10-26T08:35:30+07:00"

new Date().format("d/n/Y")
"26/10/2010"


คำถามที่น่าสนใจก็คือ เขาใช้วิธีไหนในการ implement function นี้

ลองเปิดไล่ดู code ใน file src/util/Date.js
เริ่มจาก object ที่ชื่อ formatCodes
formatCodes : {
d: "String.leftPad(this.getDate(), 2, '0')",
D: "Date.getShortDayName(this.getDay())", // get localised short day name
j: "this.getDate()",
l: "Date.dayNames[this.getDay()]",
N: "(this.getDay() ? this.getDay() : 7)",
S: "this.getSuffix()",
w: "this.getDay()",
z: "this.getDayOfYear()",
W: "String.leftPad(this.getWeekOfYear(), 2, '0')",
F: "Date.monthNames[this.getMonth()]",
m: "String.leftPad(this.getMonth() + 1, 2, '0')",
M: "Date.getShortMonthName(this.getMonth())", // get localised short month name
n: "(this.getMonth() + 1)",
....
}

คำถามที่ตามมาก็คือ ทำไมถึงเก็บ code ฝั่งขวาเป็น String

ลองไล่จาก top down บ้าง, โดยย้อยกลับไปดู function format ว่า code ข้างในเป็นอย่างไร
Date.prototype.format = Date.prototype.dateFormat;

dateFormat : function(format) {
if (Date.formatFunctions[format] == null) {
Date.createFormat(format);
}
return Date.formatFunctions[format].call(this);
},


จะเห็นว่า เมื่อได้รับ parameter เป็น format แล้ว, function มันจะพยายาม lookup หา function จากตาราง Date.formatFunctions ก่อน
ถ้าไม่เจอ ก็จะไปเรียก Date.createFormat เพื่อสร้าง function ให้ก่อน

ลองตามไปดู function createFormat บ้าง
code ที่เป็นพระเอกของเราก็คือ บรรทัดสุดท้าย
    createFormat : function(format) {
var code = [],
special = false,
ch = '';

for (var i = 0; i < format.length; ++i) {
ch = format.charAt(i);
if (!special && ch == "\\") {
special = true;
} else if (special) {
special = false;
code.push("'" + String.escape(ch) + "'");
} else {
code.push(Date.getFormatCode(ch))
}
}
Date.formatFunctions[format] = new Function("return " + code.join('+'));
},

จะเห็นว่ามัน loop ไปตามแต่ละ character ที่อยู่ใน format string ที่เราส่งให้
จากนั้นก็ไปเอาไป lookup ตาราง formatCodes ที่เราเห็นข้างบน ได้ค่ามาก็จัดการ แปะให้กลายเป็น anonymous function
แล้วก็เก็บ cache ไว้ใน Date.formatFunctions

ลอง dump Date.formatFunctions มาดู
Date.formatFunctions["d/n/Y"]
function anonymous() { return String.leftPad(this.getDate(), 2, '0')+'/'+(this.getMonth() + 1)+'/'+this.getFullYear();
}


พอเห็น code แบบนี้แล้ว การเพิ่มวันที่ไทยเข้าไปเองก็ไม่ยากแล้ว

Related link from Roti