Introduction to Functional Reactive Programming

Phải hơn nửa năm rồi tôi không viết blog, chủ đề Functional Reactive Programming mà tôi hứa với các bạn trong bài trước về Declarative Programming vẫn còn bỏ ngỏ. Mặc dù  tôi vẫn học, vẫn tìm hiểu và làm việc với FRP hàng ngày, vẫn gặp những bài toán cũng như cách giải quyết theo hướng Reactive. Nhưng quả thật tôi không muốn viết những cái nhỏ lẻ, viết lại những cái mà bạn có thể dễ dàng tìm thấy trên mạng, nhất là = tiếng Việt. Tôi muốn mình phải thật sự hiểu, phải có cách diễn đạt của riêng mình để giúp các bạn có được tư duy theo cách Reactive, thứ  mà cá nhân tôi phải mất gần 9 tháng tổng hợp, ghép nối lại.

5k iMac Mod_Small

Đó là nguyên nhân sâu xa dẫn đến việc lỡ hẹn với các bạn, còn nguyên nhân trực tiếp thôi thúc tôi triển khai chủ đề này là vừa qua tôi có tham gia Tech Talk #4 chủ  đề dành cho Mobile. Tôi đăng ký làm speaker với chủ đề FRP và được thực hiện việc trình bày trong vỏn vẹn có 5 phút, Q&A trong vòng cũng 5 phút thôi (do nói gần cuối nên phần Q&A của tôi bị cắt còn đúng 1 câu hỏi).

Và bạn biết đấy, kiến thức, kinh nghiệm bạn có được trong 9 tháng dù ít dù nhiều cũng khó lòng mà gói gọn trong vòng 5 phút được. Tôi phải bỏ bớt 1 phần demo, không cho khán giả có thời gian suy nghĩ mà bắn liên thanh, ba hoa về  Reactive, Functional, Stream, Signal... Có thể tôi đã sai khi đăng ký chủ đề mới, khó và có quá nhiều cái để nói. Tôi có thể dùng 1 slide dài hơn, đầy đủ hơn sau đó để mọi người tìm hiểu thêm. Tuy nhiên vụ #đừngimlặng làm tôi suy nghĩ nhiều lắm. Với tinh thần chia sẻ, mong muốn share để được share không cho phép tôi ngồi yên được nữa. Cần phải có 1 bài đầy đủ, chi tiết hơn để chuộc lỗi, phải giữ lời hứa với anh em. Ai thắc mắc động cơ viết bài này là gì thì tôi trả lời luôn là động cơ quyền lực nhé!

Ahihi, deep thế đủ rồi, bắt tay vào chủ đề thôi.

Do bài này là tiếp nối của Declarative Programming nên nếu bạn nào chưa đọc thì click vào đây. Bắt buộc phải đọc không thì dưới đây không hiểu gì đâu.

TL,DR: À quên, bài này có thể coi là diễn xuôi của slide nên bạn nào ngại đọc thì kéo xuống dưới cùng xem slide cũng được.

I. What is FRP?

Thật sự là giới thiệu 1 vấn đề thì có nhiều cách tiếp cận, nhưng tôi vốn là người thích lý thuyết, thích mọi thứ phải được miêu tả ngắn gọn trong 1-2 câu nên thôi cứ đi từ cơ bản. Nhất là với 1 vấn đề mới, khó + phức tạp thì nếu chúng ta có kiến thức, cái nhìn tổng quan một cách dễ hiểu và chính xác thì người đọc cũng sẽ tiếp cận nhanh hơn.

Functional Reactive Programming là mô hình lập trình(programming paradigm) kết hợp giữa Functional Programming và Reactive Programming mà ở đó concept Reactive được xây dựng trên các viên gạch là Functional. Chúng có các đặc điểm của cả 2 mô hình:

  • Functional: Closure/Lambdas, Pure Function, Composable Function(Higher – Order Function)
  • Reactive: Events/Data – Driven, Push, Asynchronous

Ai không biết những khái niệm trên là gì thì vui lòng kéo lên trên và đọc lại từ đầu 😉

Reactive sẽ trừu tượng hơn 1 chút nên chúng ta cùng nhắc lại 1 ví dụ đơn giản khi sử dụng Microsoft Excel hoặc Google Sheets. Chúng ta có 3 cell A, B, C, giá trị C = A + B.

C = A + B

Screen Shot 2016-06-01 at 1.32.48 AM

Bạn có thể dễ dàng thấy rằng khi chúng ta thay đổi giá trị của A hoặc B thì giá trị của cell C cũng sẽ tự động được tính toán mà không cần set lại nữa. Khi bạn code cũng vậy, chỉ cần gán giá trị của C = A + B một lần duy nhất, sau đó C sẽ tự động được update.

Screen Shot 2016-06-01 at 1.33.03 AM.png

ĐÓ CHÍNH LÀ REACTIVE PROGRAMMING!

 

Các bạn xin đừng vội nghĩ đây đơn giản chỉ là Observer pattern! Tin tôi đi, Reactive hay hơn nhiều! Không tin thì xem video này đi. Hay vãi đái luôn.

Wellington FP – Why Functional Reactive Programming FRP?

Những dòng giải thích trên vẫn còn quá dài và mơ hồ với cả những người chưa biết/đã biết chút ít về FRP. Có những định nghĩa khác:

FRP is about datatypes that represent a value ‘over time’

FRP is programming declaratively with time-varying values

FRP is programming with asynchronous data streams.

Nói chung là mỗi kiểu định nghĩa đều có cái hay cái dở, còn tôi thì tôi thích cách mà ReactiveCocoa giới thiệu về FRP:
FRP is programming with streams of values over time

 

1. Stream là gì?

stream

Bạn tap vào 1 button tạo ra stream tap event, bạn nhập text vào trong TextField sẽ có stream text value (String). Mọi thứ đều có thể là stream: variables, user inputs, properties, caches, data structures… gọi chung là values

stream.jpeg

Hay coi stream ở đây như là các dòng sông mà ở trên đó có cá, có hoa lục bình… Sông thượng nguồn thì hay có gỗ được con người vận chuyển về dưới xuôi… Cá, lục bình, gỗ… ở đây là chính là value, là events… Một ví dụ nữa chính là hệ thống máy tính xử sử dụng bìa đục lỗ.

MINOLTA DIGITAL CAMERA

Nếu bạn không thích ví dụ dòng sông thì bạn có thể dùng hệ thống ống dẫn nước, hệ thống băng truyền, hành lang khách sạn, lối đi trong tòa nhà… Rất nhiều cái để tưởng tượng.

Vậy stream có tác dụng gì? Chúng ta có thể chia nhánh 1 dòng sông, lọc chất thải, loại bỏ hoa lục bình trôi dạt, xây dựng các nhà máy xử lý gỗ ngay trên dòng sông… hoặc to lớn hơn nữa là sát nhập 2 dòng nhỏ làm một, xây đập thủy điện… Sông thì bắt đầu từ thượng nguồn,  nước chảy thành dòng về xuôi và đổ ra biển. Ở dưới hạ nguồn con người có thể dùng nước để tưới tiêu, đánh bắt cá, dùng gỗ làm nhà, nội thất…

Tất cả những việc làm trên cho chúng ta suy nghĩ về việc có thể dễ dàng lắng nghe (observe/subscribe), chuyển đổi (transform), kết hợp (composite)… dữ liệu.

2. Signal

Signal is a value that changes over time.

Trung tâm của FRP chính là signal. Thay vì sử dụng khái niệm variable, property… thì hãy coi chúng là các signal. Khi data thay đổi, signal sẽ tạo ra sóng chứa value lan truyền theo không gian và thời gian. Hình dung chúng ta có các cột antena có nhiệm vụ thu và phát sóng. Sóng chứa hình ảnh, âm thanh, thông tin mã hóa… Việc lắng nghe (subscribe) các tín hiệu từ signal giúp chúng ta thu nhận được dữ liệu và có các action tiếp theo.

Bạn hãy làm quen với việc sử dụng các thuật ngữ như: emits values thay vì send data, signal thay vì variable/propety…

Signal emits value over the time.

satellite

Ở một vài framework, ngôn ngữ khác nhau thì khái niệm Signal cũng khác nhau như: Observable, Event Emitter, Pipeline… Nhưng sử dụng Signal sẽ dễ hiểu hơn, phù hợp hơn với concept Stream of values. Hơn nữa Signal là thuật ngữ phổ biến được dùng trong khá nhiều ngôn ngữ như Haskell, Elm…

3. FRP is Declarative

Đến đây thì phải chắc chắn bạn đã đọc xong bài Declarative Programming rồi nhé. Xin phép nhắc lại 2 khái niệm Imperative và Declarative:

  • Imperative:  Write correct sequence of codes in the correct order. Step by step. Focus on solve problem by answer question how.
  • Declarative: Write code to describes `what`  you want to do.

FRP thuộc mô hình lập trình Declarative và nó tập trung vào miêu tả bạn cần làm gì để giải bài toán thay vì bạn cần làm các bước như thế nào . Một ví dụ nho nhỏ là khi bạn muốn duyệt 1 mảng các số nguyên.

  • Imperative: Tạo biến đếm i, tăng dần i cho đến khi i >  array.size
  • Declarative: Dùng function map để duyệt mảng.

Lợi ích của FRP và Declarative là gì chúng ta cùng sang phần tiếp theo. Demo.

II. Why is FRP?

1. Search Issue on Github

Ví dụ này thực hiện việc liệt kê tất cả các issue của 1 repository trên Github mỗi khi chúng ta gõ tên repo đó vào thanh tìm kiếm. Một ví dụ kinh điển của FRP :D. Bài toán có 1 vài yêu cầu sau nhằm tối ưu:

  • Hạn chế spam request lên API server nếu như bạn gõ quá nhanh hoặc thay đổi từ khóa liên tục.
  • Từ khóa giữa 2 lần tìm kiếm phải khác nhau
  • Bỏ qua kết quả của những lần request trước đó nếu thời gian thực hiện lâu.

Đây là cách bạn thực hiện theo Imperative:

Tôi dùng code Swift nhưng với các ngôn ngữ khác cũng sẽ tương tự. Để thỏa mãn yêu cầu bài toán, bạn cần thực hiện những việc sau:

  1. Delay giữa 2 lần tìm kiếm 0.5s
  2. Lưu lại last async task để thực hiện việc cancel nếu cần
  3. Sử dụng 2 callback lồng nhau để thực hiện tìm repository và issues

Còn đây là cách mà bạn có thể làm với FRP:

  1. Ở đây bạn có 1 signal là rx_text sẽ emit ra các chuỗi String mà bạn nhập vào.
  2. Bạn có 1 bộ điều tiết throttle thực hiện emit values 0.5s/lần
  3. Bộ lọc distinctUntilChanged đảm bảo keyword giữa 2 lần liên tiếp khác nhau
  4.  Filter các keyword rỗng
  5. Thực hiện tìm kiếm repository -> issues. Bạn có thể thấy từ khóa Latest ở đây nghĩa là chúng ta chỉ quan tâm đến request cuối cùng, các kết quả trước đó sẽ được bỏ qua.
  6. Cuối cùng filter các giá trị null

Ở đây tôi có thêm 1 vài bước kiểm tra điều kiện như keyword rỗng, loại bỏ giá trị null… để bạn có thể tự hình dung việc thêm 1 vài điều kiện nữa vào đoạn code theo cách truyền thống Imperative sẽ ảnh hưởng thế nào đến độ phức tạp của code.

Rõ ràng là code theo cách FRP làm được nhiều việc hơn mà lại ngắn gọn, xúc tích, dễ nhìn hơn, đi từ trên xuống dưới, flow rõ rằng, tập trung 1 chỗ. Chúng ta thấy là việc cancel request được thực hiện ở đúng 1 dòng code thay vì ở vài chỗ khác nhau như cách trên: lưu last request, cancel last request…

operator_throttle.JPG

throttle: các giá trị 3-4-5 quá gần nhau nên được bỏ qua và chỉ lấy giá trị cuối cùng là 5.

 

operator_flatMapLatest_2.JPG

FlatMapLatest: Thực hiện 3 request là 1-2-3 mà chỉ lấy request cuối cùng.

Flowchart:
Example1_FlowChart

Vậy lợi ích của FRP là gì?

2. Advantages

  • Code more concise & clear, easy to understand without context
  • Readability, highly express
  • Make Asynchronous easier
  • UI Binding
  • Maintainability, Extensibility

Khi sử dụng FRP, code sẽ gọn gàng hơn, ngắn gọn, xúc tích và đặc biệt là dễ hiểu mà không phụ thuộc quá nhiều ngữ cảnh (thanks to pure function).

Nhiều bạn sẽ cho rằng đó là do tôi quen với FRP rồi, nếu với những người chưa biết các operator (map, filter, reduce…) của FRP làm gì thì sẽ rất khó! Xin hỏi ngược lại các bạn là ai trong chúng ta cũng đã từng đau đầu với 2 vòng lặp lồng nhau, tệ hơn là 3, chưa kể nó còn được làm rối tung bởi đống câu lệnh rẽ nhánh if-else cùng 1 đống biến lưu trữ. Bạn sẽ phải mất bao nhiêu thời gian để hiểu được những vòng lặp này làm gì, output là gì? Những đoạn if-else này kiểm tra điều kiện gì, biến được thay đổi ở đâu? Rất mất thời gian và đau đầu. Tôi đã từng phải đọc và cũng từng code như vậy. Khi code tôi có comment đàng hoàng, đặt tên biến cẩn thận, rõ nghĩa… Nhưng tất cả đều là sự dối trá, ngụy biện cho khả năng code khó đọc hiểu, khó maintain. Code tự comment cho chính nó là tốt nhất.

Bạn quen với vòng lặp for, tôi quen vớimap, filter, reduce.

VÀ HÃY XEM AI LÀM TỐT HƠN, NHANH HƠN?

Bạn cũng có thể làm việc với các tác vụ asynchronous dễ dàng hơn, tránh được tình trạng <span style="color:#d14233;">callback-hell</span>.

FRP cũng rất hữu ích khi kết hợp với 1 số design pattern như MVP, MVVP, VIPER… nhờ việc hỗ trợ rất tốt UI Binding.

Đặc biệt, code của bạn có thể bảo trì, update hay mở rộng 1 cách dễ dàng. Hay cùng tiếp tục với ví dụ trên.

3. Search Issue on Github with Retry

Khi thực hiện các request thì có khả năng sẽ gặp lỗi, có thể là do server, do mạng chậm… và tôi muốn retry một vài lần trước khi bỏ cuộc.

  • Imperative: Sẽ phải có thêm biến đếm số lần đã retry, if-else trong failure closure.

  • FRP:

Nhìn thấy không? Tôi chỉ cần thêm mỗi hàm retry(3). NHANH CHƯA?
Đó là 1 minh chứng cho khả năng update, mở rộng khi bạn sử dụng Reactive Programming.

 

4. Example 2 – Gesture Combination

Khi người dùng thực hiện đồng thời `di chuyển` (pan) và `xoay` vật thể (rotate), thực hiện đếm ngược từ `3`.  Dừng đếm ngược khi người dùng bỏ tay ra hoặc đếm ngược đến 0.

Bạn sẽ phải làm như thế nào? `

Chắc hẳn sẽ có 1 vài ý nghĩ lóe lên trong đầu là bạn sẽ phải có 2 biến Bool đánh dấu khi nào người dùng di chuyển / xoay vật thể. Mỗi 1 lần thực hiện gesture, bạn phải kiểm tra xem điều kiện còn lại có thỏa mãn hay không… Kiểu kiểu như này.

Nếu viết thành pseudocode thì nó sẽ như thế này:

Screen Shot 2016-06-01 at 11.10.06 PM.png

Còn đây là cách giải quyết theo hướng FRP:

Ở đây bạn có 4 Signal: panStarted, panEnded, rotateStarted, rotateEnded. Việc kết hợp các sóng này với nhau tạo ra các điều kiện để Timer bắt đầu countdown cũng như điều kiện để nó dừng lại (takeUntil). Gọn gàng và dễ hiểu!

operator_merge

Merge: Gộp 2 signal làm một, lấy cả 2 giá trị.

operator_zip

Zip: Giống như khóa quần, 1 mắt bên trái sẽ đi cùng 1 mắt bên phải, luôn luôn theo cặp.

IMG_2062.JPG

TakeUntil: Cái tên nói lên tất cả. Như ở trên diagram thì Signal a sẽ lấy value đến khi nào Signal b bắt đầu emit value đầu tiên thì thôi.

 

Screen Shot 2016-06-01 at 11.09.52 PM.png

 

5. What we can do

Chúng ta có thể làm gì với các Signal/Stream hay nói cách khác chúng ta cần làm gì để giải quyết bài toán?

  • Observables – Lắng nghe thay đổi
  • Transformations – Chuyển đổi dữ liệu
  • Composition – Gộp
  • Combination – Kết hợp
  • Filter – Lọc data giống như chúng ta lọc nhiễu sóng
  • Buffer

Buffer là cái mà tôi thấy rất thích và hữu ích trong 1 số trường hợp. Hình dung đài truyền hình địa phương sẽ thu lại chương trình của đài VTV và phát lại vào 1 khung thời gian nào đó thay vì chuyển tiếp ngay và luôn. Nhờ đó, nếu chúng ta có việc bận không xem VTV được thì có thể xem lại sau đó trên kênh địa phương. Nó sẽ giúp cho bạn lấy được oldValue ở các vị trí trước đó tùy theo bufferSize.

Transformation.jpg

Một ví dụ vui về việc chuyển đổi và kết hợp dữ liệu.

III. Reactive Thinking

Sẽ có rất người có ý kiến trái chiều về FRP sau khi họ tìm hiểu khái niệm, thực hiện 1 vài demo nho nhỏ. Thích thì khỏi phải bàn nhưng mà không thích:

  • FRP khó làm quen, khó sử dụng, mất thời gian học, Imperative vẫn dễ hơn nhiều
  • FRP chỉ là xu hướng nhất thời
  • FRP chỉ là một Framework hỗ trợ
  • Nhiều trường hợp FRP trừu tượng, phức tạp một cách không cần thiết. OOP trừu tượng đã đủ nhức đầu rồi.

HarryPotter.jpg

Chúng ta được đào tạo trong nhà trường, sử dụng cách code Imperative truyền thống hàng ngày trong công việc. Rõ ràng là nó quen thuộc, dễ dàng cho người mới bắt đầu và rất khó để chúng ta có thể thoát khỏi Comfort Zone của mình. Đứa nào ý kiến là ta đấm liền!

Nhưng cũng nên tự hỏi là tại sao FRP lại đang hot thế? Rất nhiều event đề cập đến nó, nhiều công ty lớn sử dụng, đầu tư thời gian tiền bạc vào kế hoạch refactor sang FRP (Netflix là ví dụ điển hình)? Chắc hẳn nó phải có lợi ích gì khác biệt?

1. FRP is hard, but it is SIMPLE

Tôi công nhận là FRP khó.

  • Nó khó để làm quen với suy nghĩ, tư tưởng code mới, khó để chúng ta bỏ thói quen suy nghĩ theo lối cũ bao lâu nay.
  • FRP mất thời gian để làm quen, để tìm hiểu các function, operator.
  • FRP không thể học chóng vánh trong 24h hay trong  ngày được, không thể làm vài 3 ví dụ cơ bản là có thể sử dụng thành thạo được.
  • FRP khó với người mới bắt đầu và với cả người đã làm quen với nó.

Nhưng FRP đơn giản. Một khi bạn đã quen với FRP, việc đọc hiểu code, update, maintain và extend trở lên đơn giản và dễ dàng hơn bao giờ hết.

Screen Shot 2016-06-01 at 6.34.39 AM

FRP is simple, Imperative is Easy. Vậy trái nghĩa với simple là gì? Là phức tạp, là complexity? Đây là hậu quả của Complexity:

Train Fail 1.gif

Bạn xây dựng một ứng dụng từ con số không. Từng mảnh được lắp ghép, dần dần qua năm tháng, hệ thống của bạn ngày càng trở lên cồng kềnh, phức tạp và có thể sụp đổ bất cứ lúc nào. Chỉ cần 1 mắt xích nào đó bị phá vỡ, 1 dòng code nào đó bị thay đổi 1 cách vô tình do sự phức tạp của hệ thống làm người maintain không lường trước được, ứng dụng của bạn sẽ crash, treo cứng hoặc chạy sai business…

Rõ ràng việc dễ làm ai cũng thích nhưng nó cũng làm chúng ta chủ quan.

inspirationslunch-devops-och-mikroarkitekturer-marcus-ahnve-27-638.jpg

2. FRP is good trend

Screen Shot 2016-06-01 at 6.44.39 AM.png

Việc sử dụng FRP trong phát triển phần mềm giúp mọi việc phần nào trở lên dễ dàng hơn. Code trong sáng, ngắn gọn, đọc sướng mắt, thoải mái cái đầu, sửa đổi, thêm tính năng nhàn hơn, nhanh hơn… Đó là những lợi thế là FRP mang lại. Chắc chắn FRP là 1 xu thế tốt, 1 xu thế đang hot trên cộng đồng thế giới hơn 1 năm trở lại đây.

Việt Nam chúng ta trước đến nay không giỏi trong việc đi trước đón đầu, tạo ra xu thế mới trong lĩnh vực công nghệ thì ít nhất hãy làm tốt việc hội nhập, bắt kịp xu thế của thế giới. Đừng đi tắt đón đầu, lạc đấy.

Các thư viện, framework FRP được tạo ra từ 1 ý tưởng trừu tượng (abstract concept) nhưng nó đã được chứng minh là hiệu quả trong nhiều dự án từ nhỏ đến lớn, thậm chí rất to. Tất cả mọi framework đều xuất phát từ việc abstract ý tưởng nào đó, vấn đề là ý tưởng đó tốt cỡ nào, việc triển khai có đảm bảo đi đúng hướng k?

IV. How to FRP?

Bạn cần bắt đầu từ đâu, bắt đầu như thế nào?

  • Tìm hiểu thật kỹ về Functional Programming, hiểu được lợi ích của Immutable Values, Pure Function cũng như Higher Order Function… Functional Programming in Swift
  • Bắt tay ngay vào tìm hiểu và sử dụng 1 Framework FRP cho ngôn ngữ mà bạn đang dùng. Trăm hay k bằng tay quen. Hãy tập tư duy và xử lý vấn đề theo cách FRP và đặc biệt đừng mix stateful code(Imperative) và FRP. Sẽ là 1 thảm họa đấy, code thối um lên cho coi.
  • Bạn có thể kết hợp FRP với 1 vài design pattern khác như MVP, MVVM hay VIPER. Nó vừa giúp bạn học về pattern, vừa giúp bạn có kinh nghiệm giải các bài toán khi làm việc thực tế.
  • Chịu khó đọc lý thuyết lẫn thực hành. Cứ giải quyết 1 bài toán ta lại quay lại đọc các khái niệm, các blog mà trước đây chúng ta chưa thật sự hiểu. Qúa trình vừa đọc vừa làm sẽ giúp bồi đắp tư duy Reactive cho bạn, càng ngày bạn sẽ càng thấy mình trưởng thành hơn, kiến thức vững hơn. Bạn sẽ hiểu rõ stream là gì, signal có gì hay… hay các vấn đề về Maintainability, Extensibility hay sự nguy hiểm của Complexity mà tôi nhắc đến. Hãy đắm chìm vào thế giới Functional và Reactive. Ăn FRP, ngủ FRP, code chỉ FRP (sắp đến Euro rồi ;)). Một ngày không xa FRP sẽ ngấm vào máu, khắc sâu trong tâm trí bạn.
  • Chia sẻ nhỏ là có những blog, video tôi xem đi xem lại 4-5 lần, mỗi lần cách nhau 1-2 tuần hoặc 1-2 tháng. Mỗi lần xem lại là 1 lần vỡ ra thêm được chút ít. Sướng lắm :D.

Nhiều vấn đề để nói lắm mà cũng mệt lắm, không nói tiếp được.

=> CHỐT LẠI!!!

tumblr_mrwgfoq0x81stncygo1_500.gif

 

Còn về tài liệu thì nhiều lắm. Bạn có thể xem rất nhiều video trên YouTube, Realm. Hoặc có thể tìm đọc 1 vài quyển sách. Hiện tại thì tôi suggest quyển này: Functional Reactive Programming by Stephen Blackheath and Anthony Jones, tất nhiên bạn có thể sử dụng những cuốn khác phù hợp với ngôn ngữ mà bạn lựa chọn. Nhưng cốt lõi là Reactive Thinking, nó không bị phụ thuộc quá nhiều vào ngôn ngữ, thư viện.

Còn anh em nào muốn tài liệu tìm hiểu cho iOS thì vào link bên dưới hoặc contact tôi qua Skype để được share thường xuyên:

Gist: The best FRP iOS resources

Advertisements

13 thoughts on “Introduction to Functional Reactive Programming

      1. để em add skype của anh luôn ,bữa máy em bị rớt mạng tưởng tin nhắn ko gửi lên đc ai ngờ lại gủi đc . 😀 Em đang nghiên cứu Functional Programing , và có đôi cái muốn thỉnh giáo anh 😀

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s