Lately, there's been interest in "reactive programming", especially with Rx. What is Rx? I've seen descriptions like "reactive programming with LINQ", "the dual of the enumerator/iterator" and even "a variation of the continuation monad". Oh right...uh...monad? dual? what's going on?
If you like things like "monads", "duals", and category theory, go watch this video, especially until the end. It's pretty funny.
But if those things make your eyes glaze over and you're left wondering what Rx really is, I want to give you a simple explanation of what Rx is all about. In fact, I'll show how you could have invented it yourself. We'll do so with simple event-based code written in Python.
Step 1: write simple event handlers
Imagine we have a mouse click event that fires either "left" or "right", and we want to make a new event that fires "double left" when there's a double left click. We might write something like this (including a simple Event class).import time class Event: def __init__(self): self.handlers = [] def handle(self, handler): self.handlers.append(handler) def fire(self, val = None): for handler in self.handlers: handler(val) def echo(val): print val return val def left_double_click_from_click(click, threshold): dlclick = Event() last_lclick_time = [0] #closure hack def click_handler(click_value): if click_value == "left": lclick_time = time.time() if (lclick_time - last_lclick_time[0]) < threshold: dlclick.fire("double left") last_lclick_time[0] = lclick_time click.handle(click_handler) return dlclick click = Event() dlclick = left_double_click_from_click(click, .01) dlclick.handle(echo) click.fire("left") time.sleep(.02) click.fire("left") click.fire("right") click.fire("left") #prints "double click"
Step 2: refactor event handlers
It works and it's pretty simple. But, we could refactor quite a bit. If we do so, we might write something like this (notice I like the suffix "_r" for "reactive"):class EventFireRecord: def __init__(self, value, time): self.value = value self.time = time def doubleize_r(evt, threshold, combine): double_evt = Event() last_fire = EventFireRecord(None, 0) def evt_handler(value): fire_time = time.time() if (fire_time - last_fire.time) < threshold: double_evt.fire(combine(last_fire.value, value)) last_fire.__init__(value, fire_time) evt.handle(evt_handler) return double_evt def filter_r(evt, predicate): filtered_evt = Event() def evt_handler(value): if predicate(value): filtered_evt.fire(value) evt.handle(evt_handler) return filtered_evt click = Event() dlclick = doubleize_r(filter_r(click, lambda value : value == "left"), .01, lambda l1, l2: "double left") dlclick.handle(echo) click.fire("left") time.sleep(.02) click.fire("left") click.fire("right") click.fire("left") #prints "double left"That looks better and is more generic. The logic of "double click" is now contained all on one line. But, we could do even better. Notice that we repeat ourselves a little with filter_r and doublize_r. The pattern looks like this:
evt_out = Event() def handler(value): ... evt_out.fire(value) ... evt_in.handle(handler) return evt_outWhat if we refactor to pull out that common pattern by making a special handler that returns a value and a special "handle_with_result" that looks like this pattern?
def handler(value): ... return value evt_out = handle_with_result(evt_in, handler)
Step 3: make a higher-level "handle" function
If we do that, our code might look like this:def handle_with_result(evt, handler_with_result): evt_out = Event() def handler(value): result = handler_with_result(value) if result is not None: evt_out.fire(result) evt.handle(handler) return evt_out def doubleize_event(evt, threshold, combine): last_fire = EventFireRecord(None, 0) def handler(value): fire_time = time.time() try: if (fire_time - last_fire.time) < threshold: return combine(last_fire.value, value) finally: last_fire.__init__(value, fire_time) return handle_with_result(evt, handler) def filter_event(evt, predicate): def handler(value): if predicate(value): return value return handle_with_result(evt, handler) click = Event() dlclick = doubleize_event(filter_event(click, lambda value : value == "left"), .01, lambda l1, l2 : "double left") dlclick.handle(echo) click.fire("left") time.sleep(.02) click.fire("left") click.fire("right") click.fire("left") #prints "double left"It works, and our code looks better than ever. handle_with_result is very useful.
But, we are now missing something: what if we want to return multiple values? Or do something more interesting, like listen to an keyboard event and return left-clicks if the user clicks "l" and right clicks if they type "r" and two "fake" clicks if they type "f". We'd like to write something like this:
def choose_clicks(keys, clicks): def key_handler(char): if char == "l": return filter_event("left", clicks) elif char == "r": return filter_event("right", clicks) elif char == "f": return ["fake", "fake"] retrn handle_with_result(keys, key_handler)If we change handle_with_result to handle this, we might get something like this:
def handle_with_result(evt, handler_with_result): evt_out = Event() def handler(value): result = handler_with_result(value) if result is None: pass #ignore elif isinstance(result, Event): result.handle(evt_out.fire) elif isinstance(result, list): for value_out in result: evt_out.fire(value_out) else: evt_out.fire(result) evt.handle(handler) return evt_out def filter_r(evt, predicate): def handler(value): if predicate(value): return value return handle_with_result(evt, handler) def value_filter_r(evt, value): return filter_r(evt, lambda val : val == value) def choose_clicks(keys, clicks): def key_handler(char): #TODO: unsubscribe from event after either "l" or "r" if char == "l": return value_filter_r(clicks, "left") elif char == "r": return value_filter_r(clicks, "right") elif char == "f": return ["fake", "fake"] return handle_with_result(keys, key_handler) keys = Event() clicks = Event() choosen_clicks = choose_clicks(keys, clicks) choosen_clicks.handle(echo) clicks.fire("left") keys.fire("a") clicks.fire("right") keys.fire("l") clicks.fire("left") # print "left" clicks.fire("right") clicks.fire("left") # print "left" keys.fire("f") #prints "fake" and then "fake" againGreat. Now if we just add a little bit of syntax sugar to this, we can make events look like streams:
Step 4: add some syntax sugar
class Event: def __init__(self): self.handlers = [] def handle(self, handler): self.handlers.append(handler) return self #so += will work def fire(self, val = None): for handler in self.handlers: handler(val) def bind(evt, handler_with_result): evt_out = Event() def handler(value): result = handler_with_result(value) if result is not None: Event.unit(result).handle(evt_out.fire) evt.handle(handler) return evt_out @classmethod def unit(cls, val): if isinstance(val, cls): return val elif isinstance(val, list): return MockEvent(*val) else: return MockEvent(val) __rshift__ = bind class MockEvent: def __init__(self, *vals): self.vals = vals def handle(self, handler): for val in self.vals: handler(val) def doublize_r(threshold, combine): last_fire = EventFireRecord(None, 0) def handler(value): fire_time = time.time() try: if (fire_time - last_fire.time) < threshold: return combine(last_fire.value, value) finally: last_fire.__init__(value, fire_time) return handler def filter_r(predicate): def handler(value): if predicate(value): return value return handler def value_filter_r(value): return filter_r(lambda val : val == value) def click_choose_r(**clicks_by_char): def key_handler(char): return clicks_by_char.get(char) return key_handler clicks = Event() keys = Event() dlclicks = clicks >> value_filter_r("left") >> doublize_r(.01, lambda l1, l2: "double click") keys >> click_choose_r(d = dlclicks, f = ["fake", "fake"]) >> echo clicks.fire("left") clicks.fire("left") keys.fire("f") #prints "fake" and then "fake" again keys.fire("d") clicks.fire("right") clicks.fire("right") time.sleep(.02) clicks.fire("left") clicks.fire("left") #print ("left", "left")
So what have we made?
Wonderful. We've made events look like streams by making a slick way of creating event handlers. In fact, if you look closely at what I did in that last step, you'll notice that I renamed "handle_with_result" to "bind" and moved some code into a method called "unit". That's all it takes to turn Event into a monad, which is exactly what Rx does. Congratulations, we just reinvented monads and Rx, just by refactoring our event handler code and in the process we've discovered what Rx really is: a fancy way of writing event handlers, specifically event handlers that fire events that trigger other event handlers that fire events, and so on in big chain that looks like a query. So when your eyes glaze over about "duals" and "monads" and "reactive programming", just say to yourself: I'm making a fancy event handler. Because in the end, that's all you're really doing.In fact, if you want to do so in Python, now you have a basic implementation to start with! Of course, this is just a toy implementation. It lack error handling, unsubscribing, end-of-stream, and concurrency. But it ain't bad for just 50 lines of code. And it lets you see the essence of Rx fairly easily.
Oh, and what's the big deal with monads, you ask? Nothing much. It's just that if you provide "bind" and "unit" (called "select many" and "select" in LINQ, I think), LINQ gives you some nice syntax sugar that makes your event handler look like a query. It's really pretty slick, especially now that they've added "extension methods".
And next time...
In future posts, I'll explore different ways of making slick event handlers, but without monads. And hopefully we'll get make this handle concurrency, which is what asynchronous programming is all about. In fact, I expect we'll start to see a serious blurring of lines between Rx, message passing, and data flow programming.For now, when you start working with Rx, just remember: I'm making a big, fancy event handler. An "Observable" is just like and event and an "Observer" is just like an event handler.
P.S.
What's with the lame names? They started off with cool names like "Reactive" and "Rx" and then give us Observable, Observer, Subscribe, OnNext, OnDone, and OnError. Yuck. Think what an opportunity they missed! We could have had names like Emitter, Reactor, Chain, Emit, Extinguish, and Explode. Judge for yourself:observable.Subscribe(observer) observer.OnNext(val) observer.OnDone() observer.OnError(e)or
emitter.chain(reactor) reactor.emit("foo") reactor.extinguish() reactor.explode(e)
I stuck this progression in a bitbucket mercurial repository here: http://bitbucket.org/loppear/rx-python/ I hope that's alright with you. A fun exercise and a fun video to copy&paste to.
ReplyDelete(I didn't look long, but am confused why I could not get:
ReplyDeleteclick = Event()
dlclick = click >> value_filter_r("left") >> doublize_r(
.01,
lambda l1, l2 : "double left"
)
to still pass the original test with the final code. No output.
Luke,
ReplyDeleteSorry, I usually make a runnable version of the code, but I ended up splitting into two articles (I just published the latest one). My last post is the runnable version of both put together. I hope it helps.
Feel free to take the code, fix it up, improve it, post it somewhere, etc.
I looked into your repo. I don't know how your test harness works exactly. It seems weird that the tests are just comments. But, I added the event firing code directly to main.py and ran main.py and got the correct output.
Peter,
ReplyDeleteThanks for the new post. The tests are doctests http://docs.python.org/library/doctest.html although I prefer to use the Nose testrunner http://code.google.com/p/python-nose/ to collect and run tests - just run "nosetests" (or, without the setup.cfg, "nosetests --with-doctest") to see that the test passes. Not that important, just allowed me to keep the stdout-based feel of your examples without a more elaborate TestCase setup. (And of course, shortly after my second comment I realized the answer was in the question. "No output" indeed.)
I also particularly like a) this refactoring style of teaching a programming idea and b) to see refactorings through the lens of a version controlled repo.
Thanks again, I'll take some time to go through the runnable code soon.
Luke
> It's just that if you provide "bind" and "unit" (called "select many" and "select" in LINQ, I think)
ReplyDeleteLINQ's Select is actually fmap.
I guess that makes sense. For the list monad, "select" would make sense as a name for "map" (list fmap), and "select many" would make sense for "mapcat" (list bind).
ReplyDelete
ReplyDeleteارخص شركة نقل عفش بمكة
شركة نقل عفش شمال الرياض
شركة نقل عفش شرق الرياض
شركة نقل اثاث من الرياض الى المدينة المنورة
This is the most supportive blog which I have ever observed.
ReplyDeleteJava Training in Bangalore
Ui Development Training in Bangalore
It is the expect to give noteworthy information and best takes a shot at, including a perception of the regulatory cycle.
ReplyDeletehttps://360digitmg.com/course/certification-program-in-data-science
I truly like your style of blogging. I added it to my preferred's blog webpage list and will return soon…
ReplyDeletedigital marketing course
I have express a few of the articles on your website now, and I really like your style of blogging. I added it to my favorite’s blog site list and will be checking back soon…
ReplyDeletebusiness analytics course
If your looking for Online Illinois license plate sticker renewals then you have need to come to the right place.We offer the fastest Illinois license plate sticker renewals in the state.
ReplyDeleteData Science Training in Hyderabad
If your looking for Online Illinois license plate sticker renewals then you have need to come to the right place.We offer the fastest Illinois license plate sticker renewals in the state.
ReplyDeleteData Science Training in Hyderabad
Chemistry is one of the most complicated subjects and completing assignments in chemistry takes a lot of time. Students have to spend a lot of time doing their research and gathering information and molding the information into presentable form takes too much time.
ReplyDeleteAvailing Chemistry Assignment Help helps students to skip all the hard work and they can rely on the expert to do the required research and write answers. These experts are experts in the subject and they have all the required knowledge so they can complete the assignment faster and present the best answers.
I need to thank you for this very good read and i have bookmarked to check out new things from your post. Thank you very much for sharing such a useful article and will definitely saved and revisit your site.
ReplyDeleteData Science Course
It is extremely nice to see the greatest details presented in an easy and understanding manner.
ReplyDeletebusiness analytics course
This website and I conceive this internet site is really informative ! Keep on putting up!
ReplyDeletebest data science online course
Mua vé tại đại lý vé máy bay Aivivu, tham khảo
ReplyDeletesăn vé máy bay giá rẻ đi Mỹ
giá vé từ mỹ về việt nam
vé máy bay giá rẻ từ Canada về Việt Nam
vé máy bay nhật việt
dat ve may bay tu han quoc ve viet nam
Vé máy bay từ Đài Loan về VN
các khách sạn cách ly ở quảng ninh
Always so interesting to visit your site.What a great info, thank you for sharing. this will help me so much in my learning
ReplyDeletedigital marketing courses in hyderabad with placement
Very wonderful informative article. I appreciated looking at your article. Very wonderful reveal. I would like to twit this on my followers. Many thanks! .
ReplyDeleteData Analytics training in Bangalore
Thanks for sharing this.,
ReplyDeleteLeanpitch provides online training in Scrum Master during this lockdown period everyone can use it wisely.
Join Leanpitch 2 Days CSM Certification Workshop in different cities.
Scrum master certification
csm certification
certified scrum master certification
certified scrum master
agile scrum master certification
scrum master certification cost
csm certification cost
Scrum master Training
Scrum master
Best Scrum master certification
ReplyDeleteSimply want to say your article is as astonishing. The clarity to your publish is just great and that i
could think you are an expert on this subject. Well with your permission let me to take hold of
your feed to keep up to date with drawing close post. Thanks one million and please keep up the gratifying work
my website - 대구오피
(freaky)
Data backup means creating a copy of files and folders on an additional storage medium using a specific service or tool. A backup is made to restore data if the information is damaged or destroyed in the primary storage location. Creating a backup is especially important if you have your own website served by a hosting provider – in this case, pay attention to the Nakivo VM backup solution. Backup acts as a kind of lifeline in case of loss of vital data.
ReplyDeletenakivo vmware backup
ISO Certification in Oman
ReplyDeleteExcellent content ,Thanks for sharing this .,
ReplyDeleteLeanpitch provides online training in CSPO everyone can use it wisely.,
Join Leanpitch 2 Days CSPO Certification Workshop in different cities.
CSPO certification
CSPO TRAINING
This is a wonderful article, Given so much info in it, These type of articles keeps the users interest in the website, and keep on sharing more ... good luck.
ReplyDeletedata science training
I'm glad to come across this blog personal-injury-attorney
ReplyDeleteThe information you shared with us was very helpful, thank you very much. Great post, Thanks for providing us this great knowledge, Keep it up Gisinsulation
ReplyDeleteJackets on Genuine Shopping Store
ReplyDeletehttps://www.genuineshoppingstore.com
www.genuineshoppingstore.com
ReplyDeletePlease Visit
www.genuineshoppingstore.com
ReplyDeleteClick Here
www.aspireleather.com
ReplyDeleteClick Here
Click Here
Click Here
Click Here
I constantly spent my half an hour to read this web site’s content daily along with a cup
ReplyDeleteof coffee.
카지노사이트
바카라사이트
카지노사이트홈
Pretty portion of content. I simply stumbled upon your weblog and in accession capital
ReplyDeleteto say that I acquire in fact loved account your weblog posts.
Anyway I will be subscribing to your feeds
or even I success you access consistently fast.
토토사이트
안전놀이터
프로토
I must say, as a lot as I enjoyed reading what you had to say, I couldnt help but lose interest after a while.
ReplyDelete토토사이트
토토
먹튀검증
Nice and very informative blog, glad to learn something through you.
ReplyDeletedata science training in aurangabad
Awesome blog. I enjoyed reading your articles. This is truly a great read for me. I have bookmarked it and I am looking forward to reading new articles. Keep up the good work! online digital marketing course in hyderabad
ReplyDeleteI want to always read your blogs. I love them Are you also searching for nursing pico essay writers uk ? we are the best solution for you.
ReplyDeleteThat is a great tip particularly to those new to the blogosphere.
ReplyDeleteSimple but very accurate info? Thank you for sharing this one.
A must read post!
Appreciating the hard work you put into your site and detailed information you present.
Wonderful read!
토토사이트
Great blog and thanks for sharing.
ReplyDeleteMarkcon
I think about it is most required for making more on this get engaged Business Analytics Course in Dehradun
ReplyDeleteMyCaseStudyHelp.Com is an online assignment help and writing service provider for Graduate and Post Graduate students of universities. It provides quick solutions to the assignment, essay, dissertation and thesis related problems of students at an affordable service price on the following subjects: MBA, Nursing, Law, Engineering &c. Thousands of students availed our services and are happy and satisfied. Why are you lagging behind? Get more info at: https://www.mycasestudyhelp.com/assignment/
ReplyDeleteCan an event handler be used for a child class for which we have to inherit data from the parent class? If so, I would like to know how and what Data Gathering Procedure You Can Use For Thesis Research?
ReplyDeleteA very good information in this article. Everyone here to love love good article. I hope you will share more updates. Now it's time to avail limo service west palm beach for more information.
ReplyDeleteI have read your article, it is very informative and helpful for me.I admire the valuable information you offer in your articles. Thanks for posting it. Now it's time to avail Luxury Limo Services in Long Beach CA for more information.
ReplyDeleteNice post
ReplyDeleteMalaysia Translators is your go-to source for professional translation services in Malaysia. Our team of certified translators offers custom translation solutions to meet your unique requirements. Trust us for reputable translation services in Kuala Lumpur, including death certificate translation, divorce certificate translation, driving license translation, and ICA translation services. Rest assured that our accurate and reliable translations will meet your highest standards.
Thanks for sharing this informative post!
ReplyDeleteAssignmenthelper.my is the excellent provider of best thesis writing help in Malaysia. They understand that writing a thesis can be a daunting task, which is why they have assembled a team of writers who are experts in their respective fields to assist you. Their writers have years of experience in academic writing, and they are committed to delivering high-quality work that meets your expectations.