2013年12月11日 星期三

Eager & Lazy Loading

Eager Loading 就是事先將可能會用到的 resource 給準備好。
Lazy Loading 則相反,等到用到時才去fetch。

這兩個技術名詞聽起來沒什麼學問,但他代表了兩種不同的策略。何時該用哪一種是需要用經驗來判斷的,沒有孰好孰壞的問題。

2013年12月6日 星期五

用ngrok將公開網址指向你的localhost

在本機端開發網站的過程中,我們會遇到幾個情況:
要給夥伴看目前的進度
要接第三方的Oauth驗證
但如果是在家用adsl上網,會發生沒有實體ip可以接的窘境,這時我們可以使用 ngrok 的服務來產生一個公開的internet網址。

註冊Ngrok

當你沒有註冊會員時,只能隨機產生subdomain,例如:
ngrok 80 #http://580ef28f.ngrok.com

在你註冊過Ngrok的免費會員後,可以在後台取得authtoken,進而指定自己想要的subdomain:
ngrok -subdomain <your subdomain> -authtoken <your authtoken> 3000

2013年11月21日 星期四

[轉錄] 專訪引號科技團隊,十二年國教競爭力分析平台「國教通」上線 (轉自 TechOrange 橘子聚光燈)

「十二年國教」已經在今年六月由立法院三讀通過、拍板定案,2014 年正式上路。也就是說,現在就讀國三的學生,明年升高中時必須透過免試入學或特色招生的方式入學。
我們都知道,政府是在期望學生能夠全能發展的立意下推動十二年國教,然而,多如牛毛的規則始終讓其難以親近。入學方式分為免試入學與特色招生,但免試入學滿額後還要依超額比序、不同學區又各自有不同的比序,弄得家長、學生一頭霧霧水,許多家長從「搞不懂」十二年國教變成「不想搞懂」十二年國教,甚至直接把孩子丟到私立學校,一路直升,免除相關問題。自然而然大家也就感受不到政府的美意。
但大家有沒有想過,或許我們根本不必弄懂十二年國教多如牛毛的規則?
  • 十二年國教競爭力分析平台:《MilestoneS 國教通》
MilestoneS 國教通」(以下簡稱國教通)是一個幫助學生、家長、教師甚至補教業者更加了解學生學習狀況,並可以依照學生學習進度,規畫未來學習方向的平台。只要學生把想申請的學校、現有的學習進度填進系統,就能以清楚、明瞭的量化資料,了解到距離自己第一志願的不足之處,並能針對應加強之處給予建議,同時也可以一掃家長、學生對於十二年國教的一頭恐懼。
藉由國教通發表的機緣下,我們認識並訪問了其開發團隊引號科技的三個大男生:技術經理束凱文、軟體工程師劉軒銘、UX 經理劉大智。
十二年國教的規則有多複雜,我切身體驗過。在我為了瞭解國教通進而搜尋十二年國教相關資料時,也不免在幾個較為複雜的問題卡關,像是超額比序中的「就近入學」、「共同學區」等,而我相信,這些問題也同樣會困擾家長。
引號科技看見家長、學生的問題,推出台灣第一個整合全台灣 15 個不同學區超額比序資料與規則的平台。操作方式力求簡單、整潔、好上手,藉由第一次登入時的快速導覽,讓第一次使用的學生、家長可以很快地了解國教通的使用方法。
  • 國教通的優勢在哪裡?
1. 量化、圖片化(圖表化)完成程度,家長學生有目標
只需按表輸入學生的相關資料如幹部經歷、檢定證照、競賽表現等,再填入志願學區,國教通就會依照這些資料自動整理成易懂的量化模式,學生只需像完成遊戲關卡一般將表格慢慢填滿。當然填入的資料越齊全,在未來落點分析預測越準確。
2. 自主學習有方向,工欲善其事,必先利其器。
除了簡單易懂的模式與量化的圖表之外,學生還能透過國教通分析自己在十二年國教制度下的競爭力,並透過其中的客製化得分建議,清楚理解自己的不足之處,可以針對弱點處加強。
我們甚至可以預見有學生可以透過這個平台,在與家長共同規畫下,擁有清楚、明確的長短期努力方向,不必補習就可以進入自己的第一志願。這也許比一味的補習、填鴨更符合教育的本意。
3. 在未來國教通還可以 ……
隨著使用國教通的使用者越來越多,國教通可以帶來更多精準的預測,例透過越來越多人的區域、志願序甚至模擬考成績排名來做落點分析,使用的人越多,落點分析越精準。再加上引號科技也表示,未來政府若有相關政策修改,國教通也會馬上改版,調整到適合該年度政策的模式,家長與學生可以不用活在驚慌之中。
  • 規則過於複雜,家長學生卻步
訪問當天我們直接以「為什麼要做國教通?」做為開頭,這個問題由在訪問中話不多的軟體工程師劉軒銘(左)回答道:「我們一直有在關注教育方面的問題,同時也認為政府推動十二年國教是一件好事、美意,但,學生、家長卻因為相關計算的複雜性而感到卻步,這不是很可惜嗎?」
從「開始想要解決十二年國教問題」到「第一版國教通上線」,技術經理束凱文(中)向我們表示大約花了一個多月的時間,花費時間不算長,並不是他們特別「神」,而是他們之前就有過開發專案的經驗,但他們也坦承,在開發期間根本沒日沒夜,畢竟光是搞懂十二年國教的規則就花了不少時間,而他們還要把這些規則演繹成一套演算法,更要依據 15 個學區不同的計算方法來調整演算法。不過他們還是做出來了。
  • 要如何推廣國教通?
束凱文告訴我們,國教通最大的競爭對手,便是在地的相關服務。儘管國教通是第一個整合全台 15 個不同學區的系統,但部分學區已經有類似國教通的服務,外來的國教通想要打進各地早已自成一格的校務系統、補習班、學生網絡,不免有些困難。全面上線的同時,也與高雄、新竹地區校務系統商合作,整合兩地自有的校務系統直登入,為踏入各地學區的第一步。
國教通目前也積極地與提供各校模擬考的廠商洽談合作,將國教通當作模擬考的加值服務,讓考試分數直接匯入國教通系統、建檔在學生資料裡。這麼一來,除了可以蒐集到更多學生的模擬考成績,這些廠商也自然而然的成為推展國教通的業務。
  • 在我眼中,國教通還有什麼?
1. 資料的商機
我第一次了解到國教通時,除了覺得量化、圖片化複雜的十二年國規則是個非常好的主意之外,更覺得背後的商機不容小覷。如果今日國教通已經擁有部分學生的模擬考資料,是否可以針對某些科目較差的學生投放該科目的廣告?我相信使用國教通的使用者除了學生之外,家長也占了不少比例,如果有辦法進行精準行銷,非常有可能促使擔憂的家長打開錢包把小孩送去補習,這代表,補習班業者會願意打開錢包投放廣告。
除了這點之外,隨著使用者越來越多,國教通將掌握更多有價值的資料,這些資料都像是一座一座的金礦,等待挖掘。
當我們問起是否會以使用者的資料來投放不一樣的廣告時,束凱文說這還在未來的計劃中,目前首要的目標是增加使用國教通的人數。不過可以確認的是,他們絕對不會販賣、洩漏使用者的個資,而其他的廣告商機、資料商機,目前仍在規劃中。
2. 更瘋狂的填鴨式教育?
除了商機之外,使用國教通這類產品,會不會造成更瘋狂的填鴨教育?過往由於家長、學生沒有明確的目標,就是一味的補習補習補習,把每一個科目的分數衝高、追求第一志願,現在有了國教通,家長是否會按表操課,逼迫學生除了追求高分之外,還要當幹部、做志工服務以符合進入明星高中的門檻?
劉軒銘說,這絕對不是國教通的本意,也不會是教育的本意。十二年國教就是為了讓學生可以全能發展,不過擠入明星高中就是台灣目前的現況,只能期望學生們擠入明星高中之後可以好好省思一下自己未來的方向,而不是不斷地堆砌學歷。
  • 創業、工作,年經人如何抉擇?
看到這三個有理想、腦子動得快的年輕人,也不禁讓我們想了解國教通背後的開發公司,引號科技。在引號科技中負責網頁 UX 與設計的劉大智(上圖右)不厭其煩地向我們解釋了許多關於引號科技的小細節,包含引號科技目前的工作範疇:他們以由專案的 UX、流程規劃、App 開發、Data Mining 起家,曾與裕隆集團、華南銀行合作過,目前也有幾個的專案正在進行中,完全由內部主導開發的國教通便是其中之一。
引號科技目前成員只有五人,但他們不同的專案由不同的人主導,之後再向外尋找業務人選或是合作對象的方式,讓小小團隊也可以運行許多專案。例如此次的國教通專案由束凱文主導,除了來訪的軒銘與大智之外,在南部也有他們成大的學弟加入團隊運作。
引號科技的團隊人員都非常的年輕就走上創業之路,當我以「身為一個過來人,是否鼓勵年輕人自己創業」當作訪問的結尾時,束凱文語重心長地說:「最好在創業之前先工作過,才能了解到很多事情是怎麼運作的,不要抱著滿懷熱血最後卻得到一場空」。當然,這僅是他的答案,不過我相信有過不少經歷的他會這樣講,必然有一些道理。
最後,正如引號科技對自己的期許「希望可以回饋社會、為社會創造更大的價值與福祉」,這個理念我們在國教通身上看到了,也期望他們可以貫徹自己的理念,並懷抱著夢想不斷前進。
(圖片來源:人物圖片由圖片本人提供、Milestones 國教通

2013年10月20日 星期日

Riak實戰(4) - Secondary Indexes

NOSQL 不像 SQL,比較沒有 join 或針對某個 entry 的其中一個 attribute 做搜尋 (where) 的動作。通常都是以 key-value 的方式直接拿取資料,若遇到需要 join 的情況也往往是做多次的 query 拿到相關連的資料。反正概念上就是直接用 key 去對應 entry 或叫 document。
但是在使用 key 的過程中難免覺得彈性及可自定部分太少,使用起來不是這麼順手,於是 Riak 便有了 Second Indexes (2i) 的設計。

你可以將 2i 想成是 tag,讓你對特定的資料下標記,之後能更方便的 query 資料。例如我們可以從「engineers」這個 bucket 取出「language」設為「javascript」的資料。我們等等可以看看範例。


基本操作


新增並下 2i:

curl -X POST \
-H 'x-riak-index-twitter_bin: jsmith123' \
-H 'x-riak-index-email_bin: jsmith@basho.com' \
-d '...user data...' \
http://localhost:8098/buckets/users/keys/john_smith

然後這筆資料就有一個叫「twitter_bin」且值為「jsmith123」的secondary index;
以及一個叫「email_bin」且值為「jsmith@basho.com」的secondary index。


取資料 (Query):

取得特定 2i 的資料
curl http://localhost:8098/buckets/users/index/twitter_bin/jsmith123


進階操作


用範圍取資料 (Range) :

curl http://localhost:8098/buckets/users/index/field1_bin/val2/val4

範圍選取並回傳 2i (Range with terms):

curl 'http://localhost:10018/buckets/tweets/index/hashtags_bin/rock/rocl?return_terms=true'
當 return_terms=true 時,能夠連同被query到的 secondary index 一起返回


分頁 (Pagination):

相當好用,可以回傳一個token讓你再次取得下一頁的資料。
當 max_results 為 true 時,回傳結果會附上一個 continuation token,下一次的 query 只要附上這個 token,Riak 就知道要從什麼地方繼續提供資料給你。

curl 'http://localhost:10018/buckets/tweets/index/hashtags_bin/ri/ru?max_results=5&return_terms=true' | python -mjson.tool
{
    "continuation": "g2gCbQAAAAdyaXBqYWtlbQAAABIzNDkyMjA2ODcwNTcxMjk0NzM=",
    "results": [
        {
            "rice": "349222574510710785"
        },
        {
            "rickross": "349222868095217664"
        },
        {
            "ridelife": "349221819552763905"
        },
        {
            "ripjake": "349220649341952001"
        },
        {
            "ripjake": "349220687057129473"
        }
    ]
}

# Take the continuation value from the previous result set and feed it back into the query
curl 'http://localhost:10018/buckets/tweets/index/hashtags_bin/ri/ru?continuation=g2gCbQAAAAdyaXBqYWtlbQAAABIzNDkyMjA2ODcwNTcxMjk0NzM=&max_results=5&return_terms=true' | python -mjson.tool
{
    "continuation": "g2gCbQAAAAlyb2Jhc2VyaWFtAAAAEjM0OTIyMzcwMjc2NTkxMjA2NQ==",
    "results": [
        {
            "ripjake": "349221198774808579"
        },
        {
            "ripped": "349224017347100672"
        },
        {
            "roadtrip": "349221207155032066"
        },
        {
            "roastietime": "349221370724491265"
        },
        {
            "robaseria": "349223702765912065"
        }
    ]
}


效能問題

官網上有提到,當你的cluster超過512 partitions時,會有比較明顯的效能問題,這點必須注意一下。


2013年10月16日 星期三

台灣市場大?不如回來賣牛肉麵吧~

看了「台灣市場小,那市場多大才算大?」一篇文章,剛看完沒啥感覺。仔細想想後才發現自己浪費了五分鐘。

我用個簡單仿效一下這一篇的架構:
巷口的醬油很貴,多少才算便宜?

總是有人說:
「隔壁村的醬油比較好,而且又便宜」
「我們的阿婆年紀大,做出的醬油品質差」
「醬油醬油醬醬油」

……類似的觀點流傳太久、重複太多次,以致於「巷口醬油貴」幾乎成為不可質疑的觀點。但話說回來,每次聽到別人說「巷口醬油太鹹」之後,你好奇地追問:「那,醬油要多鹹才夠呢?」卻從來得不到明確答案。

總是有人說,一個村的醬油品質取決於村裡的勞動人口。我舉幾個反例吧:
隔壁村的醬油重600克,售200元。
城裡的醬油重400克,售140元。
我們的醬油1000克,300元。
你覺得呢?我不知道。

我只想當個打醬油的。 

其實大濕你的結論沒錯,因為你的文章根本沒結論

但是還是有一點營養價值的,

大概只比上面那一篇營養一點。

廢話不會錯,只是沒營養罷了。但是的確勾起了我一些想法。裡面提到統一和全家超商,說他們只靠內需,也做的很好。我同意,但這是大家都同意的廢話。那你要不要對王雪紅說,
「你們外銷做不好沒關係,你還能在台灣賣牛肉麵啊!我們內需很夠。」

這是個可行的solution,但可能不是最好的選擇。


我們來提提台灣內需市場,真的很大嗎?

統一超商夠大吧?
王品集團夠大吧?
潤泰集團夠大吧?
義美食品夠大吧?

大不大其實見人見智,如果你待的產業環繞著民生需求,也覺得上述產業做的不錯,那我想對你來說夠大了。
但就像古時候的望族或土豪一樣,大多數環繞民生需求的產業都是早期進入者,早就卡了柴米油鹽醬醋茶等等需求,口袋鼓鼓後繼續想要怎麼進一步買下這個國家人民的生存權,例如土地之類的。這個情勢在發生什麼巨大的天災人禍之前不可能逆轉。

「好吧,我們活該出生晚」

於是年輕人這麼想,他們想跳脫紅海,開始創新、開發前所未有的需求。但是問題來了,「台灣市場好像有點小,新玩意的早期使用者太少,養不起團隊」。而且就算如預期成長了,上了年紀的投資人們也會說,「你們東西很有趣,但是只做台灣賺不了幾個錢」。於是一個在美國、大陸或許能大放異彩的點子、產品就這樣在台灣胎死腹中。
市場不夠大,所以我們也不可能創造出另一個facebook、twitter或paypal。

你說我看到這篇四不像的廢文氣不氣?


廢話,內需一直都在,只是拜託不要再故步自封了

台灣一直以來都是外銷國家,從香蕉、雨傘到IC,台灣沒有太多資源但是還是能創造許多經濟奇蹟,靠的就是外銷。
內需一定一直都在,但有限,尤其是人口和產能沒有明顯增加的情況下。舉超商或餐廳來說,一家開了、一家就倒,放眼整個台灣,這樣的平衡大致是成立的。如果以目前的市場自滿,那就.....


「雪紅,趕快收收回台灣賣牛肉麵吧」


2013年10月9日 星期三

Riak實戰(3) - Link Walking

Riak中有個頗強大的功能,叫做link walking。但是在介紹這個功能之前先讓我們來談談graph DB。


什麼是Graph DB?

在台灣或許不多人接觸過graph db,但是希望大家能多少了解一下他的設計概念及使用情境。
Graph DB主要的訴求就是要解決傳統關聯式資料庫所無法處理的關係搜尋。在Graph DB的架構下,每筆資料(document形式)就是一個node,node之間可以存在edge,這個edge儲存的就是node間的關係,如下圖:

(來源:http://readwrite.com/2011/04/20/5-graph-databases-to-consider)
這樣的架構不只直覺,也克服了某些特定架構下的搜尋效能問題。舉例來說,如果你希望搜尋的是「A的朋友的朋友的親人」,這樣的架構在傳統的關聯式資料庫的實作就很複雜,因為人與人之間的關係是多對多的,一定會有個table去儲存這些關係。在我們所提到的「A的朋友的朋友的親人」的使用情境下,我們至少會做 6 次的join!( A->關係->朋友->關係->朋友->關係->親人)
這樣操作並不直覺,效能也不甚理想,所以因應這種需求的Graph DB就出現了,例如:Neo4jTitanInfinite。當然除了這編列的三種以外,還有許多graph db,甚至有些是為了生物學或特定科學領域而誕生的。



Riak 的 Link Walking

我個人覺得 Riak 為這個機制的命名實在非常生動,怎說呢?
Riak在各筆資料之間可以建立一個「單向」的 Link,透過許多link的彼此連結,我們可以再這些資料節點上「散步(walking)」,例如:
GET localhost:8091/riak/people/kevinshu/_,friend,_/_,friend,_/_,relative,_/

從上面的例子中做的事是:

  1. 我們先進入 kevinshu 這個節點
  2. 再「走」到 kevinshu 的朋友
  3. 再「走」到 kevinshu 的朋友的朋友
  4. 再「走」到 kevinshu 的朋友的朋友的親人

於是這樣傳回的結果就是「 kevinshu 的朋友們的朋友們的所有親人們」,一個看似複雜的關係就這樣透過 Link Walking 找出來了。



Link Walking 的建立與query


建立Link:

$ curl -v -XPUT http://127.0.0.1:8091/riak/people/timoreilly \
  -H 'Link: </riak/people/dhh>; riaktag="friend"' \
  -H "Content-Type: text/plain" \
  -d 'I am an excellent public speaker.'
以上的範例除了建立了 davethomas 和 timoreilly 的link外,也用 "riaktag" 來定義這段關係,如 "friend", "relative", "employee"


Query:

$ curl -v http://127.0.0.1:8091/riak/people/timoreilly/{{bucket}},{{tag}},{{Keep}}

  • Bucket: 指定某個bucket,如果這個值是 _ (underscore),則比對所有的 bucket
  • Tag: 指定 riaktag 的值,如果這個值是 _ (underscore),則比對所有的 tag
  • Keep: 0 或 1,是否回傳這個階段的搜尋結果。以一開始提到的使用情境來說,如果我只也想知道 kevinshu 的「朋友」和「朋友的朋友」是誰,我就必須在這兩個query的  Keep 值定為 1。
    如果這個值是 _ (underscore),除了在最終階段的 query 視為 1 外,中間層的query則使用預設值 0 (不回傳)。這樣講不容易懂,直接看例子:
    /riak/people/kevinshu/_,friend,_(視為0)/_,friend,_(視為0)/_,relative,_(視為1)/



Reference


  • http://docs.basho.com/riak/latest/dev/using/link-walking/
  • http://neo4j.tw/understand_graph


2013年10月6日 星期日

Riak實戰(2) - MapReduce in javascript

原理

在接收到client端來的MapReduce要求後,會將request中的process散佈給cluster中的所有node。再將inputs所指定的資料從vnode中取出,再經過「Map」、「Reduce」兩個階段的處理。一一傳給Map function,再由Reduce function給收斂起來。


處理階段


Map

這個階段會依 inputs 所指定條件中算出所有符合的 bucket-key,vnode會依bucket-key找出這些data並將他丟到Map function中做運算,回傳結果(通常是Array)。


Reduce

這個階段所拿到的input就是在Map階段算完的結果的集合。舉例來說:A data 及 B data 經過Map function運算之後的結果分別是 [1,2,2,4] 和 [3,3,4,5],在進入Reduce階段前會先被結合成 [1,2,2,4,3,3,4,5] ,再跑Reduce function。
請注意,Reduce function通常會被重複的執行,以Riak來說至少會被執行兩次,也就是再各個node reduce一次,再將各個node reduce後的結果reduce起來,成為最終的輸出。




HTTP Query Syntax


由RESTful介面向Riak下mapreduce指令的方式是POST到cluster主機的 "mapred" path,例如:
POST http://localhost:8098/mapred

而POST過去的data大約長這樣:
{"inputs":[...inputs...],"query":[...query...],"timeout": 90000}


Inputs (String or Array):

我們在下query時,在inputs這個欄位可以輸入兩種資料格式:
1. 字串:代表我要對哪個bucket做mapreduce
2. 二維陣列:對某個bucket的某筆資料做mapreduce,例:
[ ["bucket1","key1"],["bucket2","key2"],["bucket3","key3"] ]
或是:
[ ["Bucket1","Key1","KeyData1"],["Bucket2","Key2","KeyData2"],["Bucket3","Key3","KeyData3"] ]


Query (Array):

這個參數內會放一個或多個分別屬於不同階段的Mapper、Reducer或Link,格式大約有這幾種:

  • {modfun, Module, Function} where Module and Function are atoms that name an Erlang function in a specific module.
  • {qfun,Fun} where Fun is a callable fun term (closure or anonymous function).
  • {jsfun,Name} where Name is a binary that, when evaluated in Javascript, points to a built-in Javascript function.
  • {jsanon, Source} where Source is a binary that, when evaluated in Javascript is an anonymous function.
  • {jsanon, {Bucket, Key}} where the object at {Bucket, Key} contains the source for an anonymous Javascript function.

如果想把Riak的MapReduce玩熟,上面這五種格式可能需要好好看過一遍。


先來看Mapper的簡單範例:
{ "map":{"language":"javascript","source":"function(v) { return [v]; }","keep":true} }
首先會用key註明這個是map()、reduce或link,在key對應的object中則記錄了是使用「什麼語言(language)」、「執行式(source)」以及「是否在最後的結果中顯現(keep)」。language和source應該不用特別解釋,keep是個布林值,代表在這個階段運算的結果要不要呈現在最終的運算結果裡面。通常只有在最後一個階段,才會將keep設為true。

TimeOut (Integer, optional):

Map/Reduce queries 預設 timeout 為 60000 milliseconds (60 seconds),當運算超過timeout時間的時候會回傳error。我們可以依需求覆寫他(in milliseconds)。


Key filters

Key filters 可以在MapReduce之前先對input資料做預處理(pre-process),在載入資料之前事先檢查是否通過檢查條件。

使用方法

將inputs的key_filters對應到一個array,riak將會依陣列中的條件順序進行篩選,如:
{
  "inputs":{
     "bucket":"invoices"
     "key_filters":[["tokenize", "-", 1],["eq", "basho"]]
   },
   // ...
}
以上面這個例子來說,riak會先將invoice這個bucket中每筆資料的key先以"-"符號做分割(tokenize),取得「第一個」字串區段,再將這個字串與"basho"進行「完全比對」(eq)。
我們接下來就來看看在key_filter中有哪些方法可以用:

tokenize

將key依傳入的符號進行分割,再取出某個區段,通常還會接著其他條件做篩選。
"key_filters":[["tokenize", "-", 1]]

to_lower

將key的每個字母字元都轉為小寫,通常還會接著其他條件做篩選。
"key_filters":[["to_lower"]]

eq

將key與傳入字串進行完全比對。
"key_filters":[["eq", "kevin"]]

between

提供一個區段進行比對
"key_filters":[["between", "20100101", "20101231"]]

matches

篩選key中包含傳入字串的資料,這樣的比對方式叫做「wildcard」,通常會有效能上的問題。
"key_filters":[["matches", "kevin"]]

ends_with

篩選key結尾為傳入字串的資料
"key_filters":[["ends_with", "riak"]]


範例

以下的範例就是做SQL中的distinct動作,Mapper單純將自身的資料以array的形式傳回,多個map的結果concate在一起後,由reducer做distinct的動作。

{ "inputs":"goog",
  "query":[{"map":{
                "language":"javascript",
                "source":"function(value){ return [ value ];}"}
           },
           {"reduce":{
                "language":"javascript",
                "source":"function(values){ 
                              var i=0, max=values.length, r=[];
                              for(i=0;i++;i<max) {
                                  if( r.indexof(values[i])==-1 ) {r.push(values[i]);}
                              }
                              return r;
                          }"
           }]
}