Показаны сообщения с ярлыком learning. Показать все сообщения
Показаны сообщения с ярлыком learning. Показать все сообщения

суббота, 27 марта 2021 г.

Exploring a large pile of someone else's code

I am occasionally approached by students who are currently learning to program and are sometimes taken aback by assignments that look like "here is a mostly working app - go fix this and that to make it actually work". What they are looking for is some simple and reliable way to approach that huge pile of code that someone else has written and make sense of it, so that eventually it is possible to address the problems they are asked to fix.

Funny enough this not a student only problem - any working professional faces it whenever they join a new project that has already been running for some time without them. So for the sake of everyone who runs into this here are several things that I like to bear in mind whenever I run into a new code base.

1. Entry points

Any program (literally) has entry points - a limited number of code sections which connect the application to the external world. A CLI utility will normally have just one - there would be something akin to the main procedure that gets executed, whenever a user runs the utility. A web backend application will have a number of HTTP endpoints/routes which get fired whenever a corresponding request arrives (plus, again, the main function the performs the setup). A frontend application will expose a number of screens/pages available under particular routes. For example, in a React application with a router these would be some larger components mounted at particular routes. Or in the same React application you can treat the root component (who in many cases bears this proud an meaningless name App) as another such an entry point. All of these are the points in the code which get invoked by some external actions and start the journey of data and control flow through the code.

For any kind of a program you will be able to locate a number of such "entrances" and these serve as a good place to start exploration of the codebase due to a couple reasons. First, they are usually limited in quantity, which helps to concentrate. Second, and more important, when you start there, you have the benefit of understanding what happens to one side of the entry point - there is either a user or another system that performs some actions against the program being explored and thus 'calls' these entry points. This gives your exploration a better sense of direction - from an entry point you can only go deeper. Plus, it allows you to experiment - you can be that external thing - run the program a couple times or throw a couple requests at it and see how it behaves.

2. Database / persistent storage

Many applications (here I can't use that nice term "all") utilize some kind of storage - maybe they save some stuff to files or manipulate records in a database. In any case, these may aide your exploration efforts. Here you get a chance to understand what kind of data the program manipulates, how it choses to represent and store it. You may also experiment: throw a request at a web app and see how it affects its database.

The goal here is to grasp the program's data model and see how it is utilized - that illuminates a lot about the program itself. You would want to see which classes represent which kinds of data here or which functions create or read which records, and who calls them. Manipulating data is what most of the code is about, so understanding what data is at hand inevitably helps understanding the code.

3. Find yourself a simple challenge

When it comes to action, find yourself a very simple task that actually involves changing some code and do it. Maybe the original set of problems that you came here to solve includes something simple. If it doesn't - just invent it. When you're dealing with a frontend app such simple task can be tweaking the order of controls or changing the colors of some UI elements. If you work with an HTTP backend, try to add an optional filter to a GET request handler or to prevent updates for records based on any kind of criteria, which you can control.

The common place here is to keep this first task trivial - without even the goal of doing something meaningful. That's a good idea because at this stage you're still focused on getting a feeling of the code, learning to navigate it and validating that your changes actually have some effect. Once you're done with that you'll have some stable grounds under your feet: the code will feel more familiar, you'll develop some understanding of what's available to you and feel more confident about solving more complicated issues.

4. Breadth, then depth

Here is another thing that I keep returning to again and again. When facing something large you'd normally want to study what is it on higher level first, without going into details. If we speak of the entry points, identify all of them before trying to make sense of the implementation details of any particular one. When you're exploring the data model list all the tables / collections and how they are related to each other before studying in depth what and how each of them stores.

This is useful because having a broad understanding of what the whole thing deals with will help to have better ideas about the implementation details when you dive into them - having some context is always valuable. On top of that, many bits of code will work with several higher level concepts at once, so it's useful to have a rough understanding of what these are in the first place. If you get and overview of the landscape first, exploring the details will feel more like connecting the dots, rather than just wandering in the dark - don't get yourself lost too quickly.

5. Use tests

If you're lucky enough, the project that you're coming to will have some tests, which would serve as a superweapon to study it. Tests encode developer's assumptions about the code, so just reading them carefully may shed light on a lot of things. Moreover, you can change them and see what happens. And if that's not enough, you can actually play with the code itself trying to break specific things - tests will show you whether your assumptions about what breaks what are correct.

If there are no tests your position is weaker, but there is still a wat to go - try rolling up a couple unit tests for some bits of logic on your own. It turns out, testing someone else's code is the second best way to understand it, so don't ignore that. Just don't take that too far - your goal here is not to ensure 100% test coverage, but rather to make a couple assumptions about the code that you're studying and proof them right.

6. Refactor

I said testing is the second best way to make sense of unfamiliar bits of code. The best one is refactoring. In practical terms that means taking that part of code that you're interested in - a function or a class - and trying to rework it in any way that you find plausible with the main goal of making every step of it clearer to you. Don't get me wrong here - I am far from stating that it is easy to make code better when you barely understand what's going on there. The point is to try to rewrite something and thus understand it better and then just discard all of your changes. If you already have tests to support you, the effect will be tremendous - essentially you will reimplement the sections of code, which you intended to understand, and it is quite hard (although possible) to do that without developing a degree of understanding on the way. (If you don't have tests, you know what to do first).

7. Write it down

The task of fixing a couple bugs or implementing a feature in an unfamiliar repository is akin to the quest of entering a labyrinth in hope to find a couple gems in there, when you know what the gems should look like but have zero idea about the place itself. If the maze is fancy enough, finding all the gems will involve a lot of going back and forth and getting lost sometimes. To make it easier make yourself a map while you're traveling. Write down what you're after, fix your assumptions, chart the course, pin down whichever discoveries you make on your way, note what doesn't work and why.

This may sound like a lot of extra work and a major distraction, but it is not as time consuming as it sounds, while the benefits are grand. One reason, why you want to do that is it helps to get back on track when you feel lost of trying several things that don't work. Another is that taking time to make notes will help you avoid the rush of just trying to hack a couple things together. When you're familiar with the codebase doing things quickly can be fine - your deep understanding helps you make the right decisions about what to do where fast. However, while you're only making yourself comfortable with the program a more thoughtful, slow but steady approach will be more apt and will also equip you with deeper knowledge for whatever comes next.

8. Be brave and humble

Above all, don't be afraid to start and start small. Don't take a big challenge at once - break it down into smaller, easier to approach problems and handle them one by one. As long as you are taking steps, you will eventually arrive at some place. And if you take small and carefully planned steps, you will get there sooner and will see that it's the right place.

пятница, 20 марта 2015 г.

Developing a New Feature

I recently finished working a new piece of functionality on my job and it was a whole lot of experience. Even though that's not the first significant feature that I produced and I do know what the process usually looks like, some of its aspects caught me off-guard once again. It is wonderful how many surprises the process of development bears for a developer and how these same surprises continue to happen even after one passes through the process several times.

Of course it all starts with enthusiasm. If you don't hate your job, new challenging tasks always spur eagerness in you. Interestingly, the process of reading through the feature specification and long lists of requirements usually even makes it grow. The same is true about the attempts to lay out the general ideas about the solution and outline each of its components. More broadly, any planning and preparation activities increase willingness to get the problem solved and even despite seeing numerous problems, we tend to maintain motivation and good mood when getting ready for the actual work. Indeed, no matter what, we plan for success and the more we plan the more real it feels even when one sees that there are plenty of issues to overcome. One may even interpret this observation as a bit of advice - whenever you feel overwhelmed with problems and questions it may be a good idea to go and do a bit of planning - even though it might feel like you have already laid out everything, rethinking a particular piece of work might not only help understand it better, but also encourage you to do it.

However, once everything is planned and scheduled, one has to start development and produce some, preferably working, code. While the process usually starts smoothly, on some stage you smash into a seemingly impenetrable obstacle - confusion. Speaking of me, when planning I tend to thoroughly analyze various details of the future solution and study the existing bits of code, but I am not required to produce anything working right away on this stage and can be satisfied with a set of ideas and questions pinned down on a sheet of paper. On the other side, when getting down to implementation one has to squeeze new code into the mass of the application in such a manner that both old and new bits work. The sources of confusion are plenty here: sometimes you will try to add a new line of code in a method only to notice that you need to carry in 5 more arguments for it, which would make the method much less comprehensible and elegant (let alone the case when it is not elegant already). Or rather, you may find yourself in a situation when you want to change a long run of code, but feel that you miss some important ideas about it.

In my case this confusion - or maybe we should call it frustration? - is usually caused by a combination of two factors: the lack of understanding of the domain and imperfections of the legacy code on top of which (or more likely, inside which) I add new functionality. This doesn't mean that I start working on a feature or project without any knowledge of the domain - there's just always something that one doesn't understand and this is the norm. Similarly, the people who wrote the code that I have to work with are usually smart professionals, but their code is good for the task that they were solving - not always for the problem that I am currently addressing. While one cannot escape these difficulties, there is a way to tackle them - in the world of programming this is done well by refactoring the existing program or component and gradually injecting new pieces into it. While refactoring for its own sake might well be considered a bad practice, refactoring as a way to understand the code is a totally different story. Judging from my experience, there is hardly any other way to comprehend a considerably large class or method. Additionally, if you are changing the code anyway why shouldn't you make it more succinct and elegant as well?

Now, let's pretend I have paved my way around first obstacles and tackled the difficulties - what next? First of all, there are first pieces of a reward waiting for me. After I put some time into a feature, my head gradually wraps around it and I start feeling that I am able to address all the problems in a holistic and elegant manner and even make the existing solution a bit more efficient. This confidence alone greatly helps to move forward. Bad news is that these moments of high spirits usually alternate with periods when one notices more and more unforeseen issues. It feels like the more problems you solve, the more of them jump at you from nowhere. Even though good preliminary analysis and planning are supposed to reduce the amount of surprises of this flavor to minimum, they are never totally avoided. Sources might be different: sometimes a customer visits you with a shiny new idea or a forgotten nuance, in other cases you find out that a little change in one place impacts a distant component in an unpleasant way, or maybe you notice that what seemed a perfectly efficient and elegant redesign turns into an ugly muddle in light of a tiny edge case that you missed. The general idea is that one should expect the unexpected, because it always happens - usually in large amounts.

Fortunately, at some stage the task that looked like a mess of deeply intertwined problems becomes manageable and the carefully crafted solution takes the form of a solid thing. You start to address newly found problems with great ease and with even more impressing speed. You know the answers for most questions that one might ask in relation to the feature and to its neighbors. Furthermore, you are able to explain yourself and anyone willing to listen why the answers are those that you give. This is the state of expertise - both in the problem and in the solution (read implementation) - that is by itself a perfect reward for all efforts, weekends spent at the office and even sleepless nights marked with never ending search for a solution to some nasty problem. No matter whether the feature is small or large, isolated or integrated with a dozen other modules, one should feel proud when they reach this level of understanding and familiarity with it.

When the thing is almost done, however, new surprises come in. Like the final of any Hollywood movie provides some clue to a sequel, any feature added to a software product opens the door to dozens other enhancements, improvements and, well, bugs. I thought that my inability to foresee all these consequences is caused by the lack of experience, but it looks like even a group of experts can't always predict which problems a portion of new functionality may bring to existence. This basically means that there is no end to any particular feature or component. More precisely, the development can only be finished when we decide to finish it - you can't just do all of it. While this might feel depressing, there is hardly anything bad about the fact that there is always some work to be done. One should just remember this and be ready to postpone or reject some of the new ideas when they block completion of the feature. This will free you from the current burden and let you analyze all these new pieces together in a calm and reasonable manner. What's more important however, is that this way you will finally be able to mark the work item as resolved in the issue tracker or move the sticker forward on your Agile board, face the sweet feeling of achievement and have some celebration, because the thing is shipped!

While the process of developing new functionality is often an interesting and challenging activity, it might be difficult at times. You should just be ready to find yourself confused, frustrated and overwhelmed. These bad moments are simply a part of the process and their presence by no means suggests that something is wrong with the feature being developed or with you, as the developer. Eventually, these feelings will give place to satisfaction and pride with the awesome component that you deliver and with the new level of expertise that you reach through this undertaking.

пятница, 5 сентября 2014 г.

There Is No Arcane Knowledge

When starting to learn things about the world of computer programming I was sure that there is some piece of knowledge that I had to discover to become an actual programmer. My perception was that there are facts, ideas and thoughts, which were not available to me at the moment due to some reasons, but that once I learn them, it will be the moment when a programmer is born.

In childhood I used to think this way about all the things that people do: for example, I could have learnt some elementary physics in school and thought that while it looks and feels like physics, that’s certainly not something actual scientists work with. I understood that to learn what real physicists do I should go to a university, spend several years and then one day, somewhere around the fourth or fifth year they will raise the curtains and show me what is the science about and how it is totally different from what I imagined.

I don’t know whether such mindset is a common thing for children and youngsters, but it is certainly not a very good thing for students. My confidence that really important knowledge will be opened to me only at some distant point in future was a serious blocker for learning. The problem here is that when you think that the things taught to you are not that important – at least not as vital as something else that you are supposed to face later – you are not going to learn much. Additionally, having to study something useless and feeling obliged to wait for important portions of knowledge leads to frustration, at best. What’s more, the feeling that I will be taught crucial stuff in future made me believe that the only thing that I must do is waiting, while what a student (and as you gradually discover, everyone) has to do is actually learning – that is trying to do things, asking questions and looking for answers everywhere their mind can reach.

This way, it turns out that the most valuable thing that I learnt in the university, while trying to become a programmer, is that there is no arcane knowledge, which someone is concealing from me. In other words, there isn’t a piece of any particular science or profession that one has to wait to learn and that, when finally discovered, turns the student into a professional, be it a programmer or a physicist. Instead, there are lots of details behind each and every subject and each of them might seem lacking significance, not important or even irrelevant at some moments. However, the truth is that one has to study as many of these details as possible to get a feeling and to draw some unifying concepts from the experience, which means understanding what one is learning.

Once I have come to appreciate this obvious idea the game has changed and it felt like lots of doors opened, letting me learn whichever subject I found worthy. It suddenly appeared to me that the only blocker to learning new ‘real’ stuff is the limited time that one can devote to it – apart from that you can go anywhere and study whatever you wish. A good finding, ain’t it?

The nasty part is that I still sometimes have to remind myself that there is no knowledge that someone is trying to hide from me, preventing me from being a better programmer, advancing from a junior to a senior position and so on. As before, I sometimes find myself thinking that one day I will learn the last piece required to be a great programmer and will finally turn into one, although I have already got used to telling myself that becoming a better programmer is not about studying a certain amount of required things. I will keep facing the idea that something is not available to me yet, but, unlike with my first years at the university, now I know that the reason is not that the knowledge is hidden, but that I didn't try the right things and didn’t ask the right questions yet. Hence, I should keep asking new ones and looking for answers to them, because that’s the only way to learn something new. What’s even more important, this process of searching for answers and bringing them to the world is what delivers me the experience and being a professional is more about the experience than anything else.

This said, eager to become a great software developer, instead of waiting for someone to teach me a magic trick or two I will better sketch some design diagrams and put together a bit of code – that seems to be the only reasonable way to achieve my goals.


четверг, 22 мая 2014 г.

You Can Learn While You Are Programming

When entering my sixth year at the university a couple years ago I faced a very severe problem. That was the time when new MOOCs started appearing at the speed of light: Udacity was dense with Sebastian Thrun, Peter Norvig and other awesome folks, while Coursera simply shipped dozens of new courses from major universities each month. The abundance of high quality learning materials as well as the freshness of the idea that you can freely learn whatever you want from the world’s best tutors caught me unarmed and I spent a lot of time just watching what I could learn in addition to actually putting effort into some of the courses. However, gradually a concern started building up in me: I felt like I put all my energy into learning instead of creating something new (this is obviously a fallacy, because I just wasted a great deal of my time on other useless stuff.) At that time I had a part-time job, which made me do stuff, but somehow I still thought that my time, which I could devote to producing new things, is wasted on merely listening to lecturers and doing quizzes and homeworks. Quite quickly, this idea led me to giving up all the MOOCs that I had engaged into and switching my efforts to the attempts to create something worthy for the awesome Windows 8 platform. I ended up hacking a brand new task manager app, which would have definitely become cool, should it ever enter the Windows Store. I really put a lot of time into this thing and pushed it quite far – at least that's what it looked like for me – but all that was not enough to make an actual product of it. Somehow, my attempts to become a producer instead of a student only taught me a great deal of things, but did not end with delivering goods.

These days things look quite different. First of all, I have a full-time job which eats a solid share of my time and energy, but also gives the feeling of involvement into creation of something large and cool in exchange. Besides, over the last months I have built a couple Windows Phone apps to fulfill my old desire to be a creator and deliver content to the public. I also engaged into other activities, which feed on my free time, so little of it is left to learning. However, as one might guess from my long post about MOOCs, this all led me to worrying about the fact that I don’t allocate enough resources to expanding my education, firming its basement and exploring new grounds. From time to time I literally feel the lack of inflow of fundamental knowledge to solidify my education. Not that it prevents me from working or makes my life unbearable, but once or twice a week I do stumble upon the thought that I should spend some time doing online courses, which could fill the gaps in my education and make me think on the problems different from those bothering me in my developer’s life. Looks familiar, huh?

This way I keep oscillating between the two goals – learning more and producing more – and constantly thinking that I don’t work hard enough towards one of them. Honestly speaking, even though this sometimes makes me nervous and angry with myself I am grateful to my subconscious for bothering me. When I lean too much on the learning side, my mind kindly reminds me that there is a lot that a programmer aged 24 can produce to his or her and others benefit in the world, where software brings new opportunities and solves real problems every day. On the other side, once I engage too eagerly into creating (mostly useless) stuff, I soon get reminded that to stay afloat in our rapidly changing industry one has to keep learning constantly and do this not only to be on the front edge of technologies and Javascript frameworks, but also to learn and remember how to learn. For me this latter activity involves exposing myself to some fundamental science and ideas, which can serve as a good basement for applied programming stuff that I need to deliver great software. The problem here is that our time and energy are limited even when we are still young and thus it may seem difficult to do well in both areas. Seriously, if you are a lucky prisoner of 5x9 job and try to, for example, create some lovely mobile apps during your free time, it is hard to stick something like a dense Quantum Physics course into the schedule even if you can attend it in your bathroom or elsewhere. I didn’t manage to do this with Quantum Physics, but I have positive experience with a couple other courses as well as a few tutorials and must admit that it is much easier to combine studying and producing than it might seem at the first glance.

First of all, to be able to both learn and create I absolutely had to set proper goals for a long period of time – at least for the year 2014. In terms of MOOCs I have quite a modest target of passing 5 courses by the end of December. I am not sure if that’s enough to satisfy my appetite for new knowledge, but at the same time I am confident that trying to do more won’t work out well in combination with other challenges that I have chosen to face. For now I have done a course and about a half of another one, which is a bit less than what a uniform schedule would require. Still, I believe I will be able to deliver on my goal, should I increase the pace just a little bit. On the other side, my targets for learning applied stuff like languages, libraries and frameworks are not that measurable and clear, but this is compensated by the way I approach them.

The thing that really helped me learn a lot and keep going with my applications at the same time is a routine that I have developed. On a typical workday before showing up in the office I would usually spend about an hour with my laptop in a coffee-shop and that’s the time that I can devote to doing tutorials on AngularJS or playing with Clojure macros. The trick here is that I know that I can spend my morning either learning some stuff this way or working on a future post for my blog. Since I have a pretty defined schedule for the blog, a lot of mornings are left for studying new tools and that’s just cool. This approach helps a lot because I always have some time to put into “applied education” and in addition to this I also make myself finally wake up and get ready for job before actually appearing at the office.

With MOOCs the story is a bit different: I tend to allocate time for watching lectures and doing related exercises on weekends - particularly because they usually require longer and more focused timespans than just doing a tutorial or hacking some code. The fact that weekends come only once a week encourages me to put some time into a course on Sunday, because I know that if I don’t do this, the next chance will be no sooner than in 5 days. On the other side, because with online courses I am not as rigorous as with morning tutorials and have much more options to start my Sunday, I perform a bit worse here.

So, over the last years I have finally learnt that despite having limited time we still can and should put effort into both creating and learning. Fortunately for the educational side of things, we typically have a lot of time during any particular day that we can’t devote to creative activities, and which can be spent exploring new fields. When one has these periods depends on the person and their daily schedule: for me that’s usually mornings when I am not yet ready to think really hard and produce something. For someone else the best time to study may be, for example, in the evening because one is too tired from job and the whole day of debugging SQL scripts. Identifying these ‘non-productive’ intervals in your schedule and making yourself routinely use them for learning can really help expand your education and develop yourself professionally. On the other side, I feel that the contrast between learning and producing that bothers me so much is to some extent made up. In the end, when striving to deliver something new one will inevitably have to study a lot – I definitely did learn much from my Windows Phone affairs.

So, how do you satisfy your natural need for learning new stuff and balance it with delivering awesome products? What are the routines that help you do both these things and leave some time for entertainment and personal life? Please tell me in the comments – knowing how other people organize their time and work towards their goals can really help others a lot!

пятница, 9 мая 2014 г.

How to Make an SQL from Clojure or My Macros Kata

Despite the fact that I started learning Clojure about 2 years ago, until recently I didn’t explore, maybe, the most fascinating part of the language – macros. I did try to compose simple macros once or twice, but generally they failed to grasp my attention and I never understood how they work and, more importantly, when should I use them. To fix this, last month I decided to implement something interesting with the help of macros and here I will show what I did and sum up what I learnt.

The example that I’ve taken might be not the most suitable for the goal, but it worked out quite well. The idea was to allow for writing SQL-like queries against the lists of Clojure dictionaries or records – basically anything that allows key-based access to elements. The value of such a thing is doubtful, but SQL is a well-known Domain Specific Language and the fact that macros are considered a great tool for creating new languages in Clojure made me chose it as an exercise. Besides, I remember that there was an exercise like this – similarly with SQL-like queries – in the Joy of Clojure book, which made me think that this task suits my learning goals well. However, I intentionally avoided peeking into the book to not spoil my exercise and, kind of, do everything myself without borrowing others’ ideas and solutions.

The results at which I arrived are quite fascinating: I am now able to write almost real SQL against lists of records. The key difference between my queries and SQL is the use of keywords as the names of fields to select and filter on. In addition to that, well, my selects come in parenthesis, because I am still writing LISP. Otherwise, everything looks very similar to the queries one would execute against a database and that feels pretty awesome. Now, let me share my excitement and show you the code.

In the first article we will begin with simply selecting some fields from the records and add where clause for filtering. In the second one I will show how we can enable joining tables together and ordering the results. So, what we want to achieve at the first step is to be able to write a query like this:

(def table [
  {:a 1 :b 100 :c "100" :d 4}
  {:a 2 :b 200 :c "200" :d 3}
  {:a 3 :b 300 :c "300" :d 2}
  {:a 4 :b 400 :c "400" :d 1}])

(select :a :b from table)

This is not a very difficult task, because all that we have to do is limit the set of key-value pairs in each dictionary in the resulting list to only the keys, which are specified in the query. On the other hand, we have this from symbol to treat, but that’s quite easy as well.

(defmacro select [& what]
  (let [fields (set 
          (take-while #(not= 'from %) what))
      source (fnext 
          (drop-while #(not= 'from %) what))]
      `(map (fn [record#]
            (into {} (filter #(~fields (first %)) record#)))
          ~source)))

It turns out that similarly to the functions I can’t easily write a macro, which solves precisely one problem, so several things are going on in select. First, we have to get hold of a set of keys denoting fields to select. All of them come before the from word and for now we can assume that everything that goes before it is a field name. This said, we just take elements of the argument list until we meet from, push them into a set and bind it to the fields symbol. In a very similar fashion we extract the table, from which we want to select records – I expect that it is found right next to from, so I skip everything until from as well as from itself and grab the first element of the remaining sequence. Having fields and source, we can actually define what our macro will return to the caller. We simply map a function that filters each record according to the presence of the key in the fields set and composes a hashmap from the resulting key-value pairs  onto the “table”. Although this doesn’t feel like the most efficient and nice solution, I’m okay with it for now.

Because we are writing a macro we want to return something that will be evaluated upon execution of the program – that’s why we syntax-quote our map call with the `. Syntax-quoting prevents the form from being evaluated, but unlike simple quote (‘) it allows us to avoid unquoting map, fn and all the other stuff that is used inside it. One of the problems with quoting surfaces when we want to use a binding inside the body of a macro – in our case with fn. The idea is that binding requires a symbol and we need to introduce it somehow. There are several alternatives, but the simplest one is to use the gensym thing through suffixing a symbol with # character. When encountered with it inside a syntax-quoted form, Clojure will generate a non-qualified symbol, which we can then use. Finally, in select we need to unquote fields and source so that proper stuff will be inserted in the respective places. If you forget to unquote these symbols, syntax reader will try to resolve them to fully-qualified ones. This may result either in a failure in case you don’t have such a symbol outside the macro, or in a subtle failure – if you have one. The latter situation might lead to great confusion: the code that used to work will at some moment get broken because its surroundings have changed. Fortunately, in most cases such an error will surface quite soon if not instantly. Having done all these things right, we can check whether they work and, fortunately, they do:

(select :a :b from table)
;=> ({:a 1, :b 100} {:a 2, :b 200} {:a 3, :b 300} {:a 4, :b 400})

Let us try to understand what did macro allow us to do here that a function wouldn’t permit. Most notable thing is our use of the from symbol. Despite the fact that it is not bound to anything we can still utilize it in the macro – as a separator. A function wouldn’t allow that because it would try to evaluate all the arguments passed into it and fail with from. Macros, on the other side, receive their arguments in unevaluated form, which allows us to play with them in different and strange ways. Note that we could achieve the same behavior with a function if we used, for example, a keyword :from instead of the pure symbol, so there is actually little value in using macros like this. However, knowing that Clojure allows us to write down some utterance and can live with it is pleasant and inspiring.

Now, when we can select stuff, let us introduce filtering. This task is less trivial not only due to more complex constructs needed for conditions, but also because they will come in infix notation, which is natural for SQL, but hardly a common thing in LISPy Clojure. Even though this may sound difficult, it boils down mainly to tossing the list of arguments and just requires covering a couple of cases and careful use of recursion.

To make filtering possible we will define a macro that creates a Clojure-evaluatable condition from an SQL-like one. First, we will try to cover the most simple case – something like select :a :b from table where :a < :d. Our new macro – let’s call it condition – will get the :a < :d part of the above query from the select and return something like (< (:a table) (:d table)) – the piece, which Clojure will easily consume. The first version is going to be ve-e-ery simple:

(defmacro condition [t k1 op k2]
  `(~op (~k1 ~t) (~k2 ~t)))

The only thing that we do here is changing infix notation into prefix one and accessing the values from a record with the keys specified in the condition. We can use macroexpand to check that condition easily treats simple cases:

(macroexpand '(condition table :a < :d))
;=> (< (:a table) (:d table))

(macroexpand '(condition table :b = :c))
;=> (= (:b table) (:c table))

The fact that the macro fits easily into one line makes me ask myself whether there is any reason to writing it. However, instead of trying to find an answer I will simply fix the cause and… make it longer. One thing that condition misses is the ability to consume literals. For example, it is pretty natural to write a filter like this: where :a > 2 and we definitely should support this. We will do it the simple way – if we face a keyword in a condition, we will use it to get the corresponding value from the record and inject it in the resulting code. Otherwise, we won’t do any transformations to the value and will include it into the result as is:

(defmacro condition [t k1 op k2]
  (let [v1 (if (keyword? k1) `(~k1 ~t) k1)
      v2 (if (keyword? k2) `(~k2 ~t) k2)]
  `(~op ~v1 ~v2)))

Now condition treats both keys, which denote fields of the records, and mere numbers or strings equally well allowing us to filter both based on relationships between fields and on their comparison with some external values. What makes me feel particularly great about the new version of the macro is that there are no checks for keywords in the code that it outputs – it determines whether it looks at a keyword in the let form, which is not a part of the resulting code. This makes the output of the macro shorter and a bit more efficient. To see it we can do macroexpand as usual:

(macroexpand '(condition table :d not= :a))
;=> (not= (:d table) (:a table))
; no checks for keyword, table accessed in both cases

(macroexpand '(condition table :a < 3))
;=> (< (:a table) 3)
; no checks for keyword, and table accessed only once
; while for 3 we have just 3

Our condition macro can’t handle neither complex nor long conditions so far. We will make it almost universal a little bit later, but now let’s take a step back and assemble condition and select together. When faced with this task first I did introduce another level of indirection in the form of additional helper function, which would wrap the condition. However, it turns out that one can do this with much less clutter. After some experiments, I ended up using a lambda expression in the body of select:

(defmacro select [& what]
  (let [fields (set 
          (take-while #(not= 'from %) what))
      source (fnext 
          (drop-while #(not= 'from %) what))
      conditions (next (drop-while #(not= 'where %) what))]
      `(map (fn [record#]
            (into {} (filter #(~fields (first %)) record#)))
          (filter #(condition % ~@conditions) ~source)))) 

In the snippet above we have added another binding to take hold of the conditions passed into select. We assume that they go after the where word and thus extract them from the arguments list similarly to the fields keys and the table. Then, in the body of the macro we introduce filtering, which is performed with a lambda making use of our condition macro and the list of conditions in infix notation. The most surprising part is that this thing actually works:

(select :a :b from table where :a < :d)
;=> ({:a 1, :b 100} {:a 2, :b 200})
(select :d :c from table where :d >= 3)
;=> ({:c "100", :d 4} {:c "200", :d 3})
(select :a :b :c :d from table where :a = :b)
;=> ()

That’s a wonderful achievement, we can write SQL queries in Clojure! Well, almost. For now, we miss a couple things including joins, ordering and more complex conditions. The latter seem to be the most natural thing to address next, so let us enable queries like (select :a :b from table where :a <= 2 or :c = “400”). At the current stage trying to execute something like this will lead us to a “Wrong number of arguments passed to the condition macro” error, which is a not so often case when the error message is actually helpful. We do not in fact allow conditions composed of more than three elements and that’s what we will fix right now.

The most trivial way to allow for the longer conditions like :a <= 2 or :c = “400” is to introduce another version of condition macro, which would take 8 arguments: [t k1 op1 k2 link k3 op2 k4]. Here k3 and k4 are either field keys or values (:c and “400” in the above example) similar to k1 and k2, op2 is a comparison operator (=) like the op1 and link is a Boolean function (or), which glues two parts together. Now, 8 arguments is quite too much even if we try to bring SQL into Clojure. If you don’t think so, it is not difficult to come up with a three-part condition which would require 12 arguments with even more stupid names. Fortunately, we don’t have to write all these things out because the similarity between k3 op2 k4 and k1 op1 k2 suggests that we can generalize the implementation to allow arbitrary long conditions with quite a short and concise piece of code:

(defmacro condition 
  ([t k1 op k2]
    (let [v1 (if (keyword? k1) `(~k1 ~t) k1)
        v2 (if (keyword? k2) `(~k2 ~t) k2)]
    `(~op ~v1 ~v2)))
  ([t k1 op1 k2 link & other]
    `(~link
      (condition ~t ~k1 ~op1 ~k2)
      (condition ~t ~@other))))

First of all, the version with 4 arguments remains intact meaning that we don’t break the existing code, which is great. Another good thing to note, is that we reuse it inside the version with the longer argument list – there is no need to write the checks for keywords and move arguments once more, given that we have written this code before. Moreover, this new version of condition might reuse itself in case the other part of arguments list includes more than three elements. This basically means that in addition to the original goal we just allowed writing conditions like :a = 1 or :a = 2 or :a = 3 or :a = 5 or :a = 6 or :a = 7 at no cost. Recursion is a great thing by itself, but it is even greater when combined with argument lists of arbitrary length. Especially so when it works:

(select :a :b from table where :a <= 2 or :c = “400”)
;=> ({:a 1, :b 100} {:a 2, :b 200} {:a 4, :b 400})

(select :a :b from table where :a = 1 or :a = 2 or :a = 4 or :a = 5 or :a = 6 or :a = 7)
;=> ({:a 1, :b 100} {:a 2, :b 200} {:a 4, :b 400})

This all looks incredible, but there is another crucial thing that we miss – conditions can be complex, which means that we might want to include prioritization with parenthesis into them. This could be a difficult and messy thing to implement should we write in something that is not LISP. However, with Clojure macros everything wrapped in parenthesis is a list and can be treated as a list. The incredible power behind this idea becomes clear once we see how little code we have to add to the existing condition macro to  dramatically increase allowed complexity of conditions:

(defmacro condition
  ([t complex]
    `(condition ~t ~@complex))
  ([t k1 op k2]
    (let [v1 (if (keyword? k1) `(~k1 ~t) k1)
        v2 (if (keyword? k2) `(~k2 ~t) k2)]
    `(~op ~v1 ~v2)))
  ([t k1 op1 k2 link & other]
    `(~link
      (condition ~t ~k1 ~op1 ~k2)
      (condition ~t ~@other))))

As you can see, the only new thing in the macro is another override accepting two arguments: the usual record t and a complex condition. Having taken the complex part it basically unwraps it and calls itself with the extracted elements. As a positive side effect, this little addition drops the optional parenthesis in case someone decides to surround the entire restrictions list with them.

(select :a :b from table where :a = 2 or (:d < 3 and :b > 300))
;=> ({:a 2, :b 200} {:a 4, :b 400})

(select :a :b from table where (:a <= 2 or :c = “400”))
;=> ({:a 1, :b 100} {:a 2, :b 200} {:a 4, :b 400})

Once again, recursion and pattern matching do a lot of hard work for us. However, even though this feels like magic, it isn’t and there is an input that we should definitely handle, but don’t treat well so far. That’s the first of the above examples but with reversed order of restrictions: (select :a :b from table where (:d < 3 and :b > 300) or :a = 2) yields an error. The problem is that we allow parenthesis only in the tail position of the conditions list. On the other side, should we do TDD, tests would tell us that we have already broken the select macro – it doesn’t work without filtering anymore, because no matter if there is the where word and something after it in the arguments list, we always try to construct a filter. Since condition macro doesn’t work with a single argument, record, it rightfully fails. Here is the final version of the macro, which addresses both issues:

(defmacro condition
  ([t]
    `true)
  ([t complex]
    `(condition ~t ~@complex))
  ([t k1 op k2]
    (if (seq? k1)
      `(~op
        (condition ~t ~k1)
        (condition ~t ~k2))
      (let [v1 (if (keyword? k1) `(~k1 ~t) k1)
          v2 (if (keyword? k2) `(~k2 ~t) k2)]
      `(~op ~v1 ~v2))))
  ([t k1 op1 k2 link & other]
    (if (seq? k1)
      `(~op1 
        (condition ~t ~k1)
        (condition ~t ~k2 ~link ~@other))
      `(~link
        (condition ~t ~k1 ~op1 ~k2)
        (condition ~t ~@other)))))

To handle queries without filtering we simply introduce another overload of condition for empty restriction list that returns quoted truth (this even sounds cool!). When we invoke it from select, true ends up in the body of the filter function making it allow every record that it sees. As for making parenthesis work in any position, to allow this we have to do a little bit more work. The idea is that when introducing prioritization we made our older assumptions about what is a comparable value (k1, k2, etc.), what is a comparison operator (op, op1 and op2) and what is a Boolean link between several restrictions fallible. However, to fix this we can just check whether we look at something enclosed in parenthesis and handle the thing accordingly if we do. That’s what the new seq? parts are for. Even though this makes the macro grow in size significantly, I believe that’s a fair price for almost universally working solution:

(select :a :b from table where (:d < 3 and :b > 300) or :a = 2)
;=> ({:a 2, :b 200} {:a 4, :b 400})

(select :c from table)
;=> ({:c "100"} {:c "200"} {:c "300"} {:c "400"})

Now, that we achieved an important milestone of selecting stuff from lists of records or dictionaries and filtering it freely, let me sum up what I learnt from the exercise:
  1. Macros really make one think in a different way than when composing mere functions. Even though it is hard to communicate this feeling, writing code that produces code is whole another sensation than writing code that produces data.
  2. On the other side, Clojure does its best to make code as similar to data as possible. Storing programs in lists is cool, because here and there this saves you from extra difficulties.
  3. Arguments passed to a Clojure function are evaluated first; those passed to a macro are not. This makes a huge difference.  
  4. You can’t apply a macro, but you can splice-unquote arguments for it. 
  5. In a macro you can have code that it will return – usually quoted – and code that determines what will be returned and does not show up in the macro’s result. 
  6. Doing katas is worth the effort even if you don’t feel like it helps you understand anything. If something feels too much for you just force yourself into it and logic will gradually surface. To some extent learning is like violence. 
  7. If you don’t refactor, you might be missing a chance to understand both your code and your tools.
  8. Explaining what you have done in a sum-up blog entry might feel stupid, but if you are not doing this you are maybe missing another chance to understand both your code and your tools.

For now I didn’t cover a couple of things, which one expects from SQL. Most important are joins and ordering – these I will implement in the next article. Besides, there are a lot of minor features like, for example, selecting all the fields from a table with a star (*). This particular one is extremely easy to introduce, so I won’t show it here – you can take a look at the code on GitHub if you want.

Note that the above GitHub repository differs in some ways from the code shown in this post. If you want the latter, just grab this single file – zero dependencies is great.

I have also discussed my affair with macros in the recent episode of our Code Speak Loop podcast with my co-host Michael Pankov.

пятница, 7 февраля 2014 г.

Learning: MOOCs

I first attempted to do MOOCs when the whole thing started with Andrew Ng’s ‘Machine Learning’ and Sebastian Thrun’s ‘Introduction to AI’ back in 2011. For sure, both the ‘Machine Learning’ that I chose to take then and the courses, which followed, impacted my life significantly not only allowing me to explore loads of interesting stuff, but also enhancing my study at the university and to some extent forming the scope of my interests. This made me think a lot about the new phenomena of MOOCs and how they fit into our life and the world of education.

воскресенье, 27 октября 2013 г.

Advice: Don't Do It Right

I remember when, years ago, I was at elementary school I got an important advice from my grandmother. Once, checking my homework she noted that I should pay more attention and care to it, despite the illusion that doing it faster saves me time. The problem was that I didn’t want to do homework – it bored me – what I wanted was playing video games, taking a walk or watching some Discovery channel movies about science and hi-tech. With homework I pursued the sole purpose – to get rid of stupid exercises in arithmetic and Russian as fast as possible and get to worthy things. Obviously, this impacted the quality, which was difficult not to notice for my granny and thus she tried to help and motivate me to do better. She used quite a powerful argument saying that everything that’s not done right will have to be redone later. So being more careful and focused from the beginning I will be able to avoid returning and save a lot of time for great things even though this might not look this way at the first glance. I did grasped the idea and, although sometimes I failed to focus on boring stuff giving way to mistakes, I kept in mind the notion that one should take care to do everything the right way so that they don’t have to come back to it long after.

Still, now it seems to me that I got the advice wrong and took it too far, which produced a very bad side effect on the way I work. What I mean is that when trying to create something that is important and interesting to me I become obsessed with the quality of the thing long before it even begins to emerge. This way I quickly find myself in exhaustive search for the right ways to implement every tiny piece of what I am trying to accomplish and during its course I usually can’t allow myself to sit down and build something little and potentially dirty, because I remember this is a waste of my time. One may think that is great and helps me create nice and wonderful stuff, however the reality is harsher. This chase for perfection giving birth to the fear of dirt actually raises a stone wall between me and long waited moment of achieving my goals.

The reason for this is that creating means learning. Learning, in turn, can’t take place without building dirty things and making them better – as far as I know, there isn’t such a thing like a direct way to mastering something without making a stupid mistake or two first. It took me long to accustom myself with this idea, see how it hampers my own work and learn to spend less time on weighing infinite number of alternatives, throwing them away and finding and weighing some more, again and again. I have many times faced this problem when writing posts for the blog, but eventually I have developed a habit of quickly hacking a couple of dirty sketches without concerns of the language and other aspects of quality. Doing these I literally push myself to leave white spaces and ignore the fact that some of my phrases feel Hulkish at best – this way I can proceed knowing that later, when I see the full picture clearly, I will return and make the ugly parts polished and shiny. Such an approach saves me a lot of time, which would otherwise be wasted on searching for words that are yet to come and expressing the ideas, which I realize only partially. In fact, there is nothing bad or inefficient in producing a thing that lacks elegance or even correctness on some stages of the process. Moreover, doing so doesn’t hurt the quality of the final product at all, as long as one remembers to return this debt later.

Fellow coders might have guessed that I learnt this approach through programming. Kent Beck’s ‘Test-Driven Development: By Example’ book was the first thing to introduce me to the idea condensed in the words "first make it work, then refactor". While Kent deals mostly with the low level of code in the context of solving a specific bit of a problem, the advice is also applicable to most other levels and aspects of the software development process. I find it particularly helpful when working with new technologies – languages, frameworks, libraries and whatever else: in many cases spending a lot of time on trying to figure out the abstract right ways to use the tools turns out to be a major waste of resources of any kind. On the other side, actually taking what you have and building something, maybe so ugly and stupid that even stackoverflow won’t bear its presence – but working, provides one with better understanding of both means and goals. Honestly speaking, I rarely realize what I want from a particular tool until I actually try to use it some way, thus the chase for perfection ‘from start to end’, although may produce something nice, most likely won’t lead me to what I wanted. Moreover, there usually exists a chance that I will fail to create anything meaningful at all.

Perfection requires major investments and a firm basement, which is to be built from something common and dirty. In the end, it is quite difficult to claim a cathedral a piece of art while it is being constructed from stone, steel, cement or clay, but no one will remember what its naked walls and empty windows looked like once it is finished. I believe that years ago my granny didn’t want to make me do my homework perfectly correct right away. What she meant was that I should start whichever way I like but not stop until the thing looks beautiful. 

суббота, 2 марта 2013 г.

Tutorials Are Not That Bad

From the time when I started to study programming in earnest I somehow disdained tutorials of any kind. There are dozens of them in the world of programming as well as in any other field involving operations performed by means of a personal computer. These tutorials composed of exercises or tasks, which end up being long lists of "go-there-type-this-click-that"s, sometimes interleaved with descriptions of "note-how-the-thing-changes" kind jump at you whenever you type a query involving a name of some technology or tool in Google. Moreover, almost every book dealing with some technology - a programming language, for instance, - contains such exercises. Maybe, it is even safe to say that the more exercises a book of this kind contains the better it is, so I don't state that all tutorials are useless and hardly deserve any attention and I am not even going to discuss the wide range of their quality. What I say is basically that during several years of study I used to think that an exercise inviting me to reproduce its author's actions is not worth the effort.
 
Actually, most of the tutorials, which I came across while exploring various languages and techniques, were very helpful and provided me with valuable insights, thus leaving me no reasons to lament the time spent. Furthermore, I really appreciate the work of people who produce such things just to make their knowledge and expertise accessible to others. Still, I had a reason or an excuse - the latter sounds closer to reality - to avoid tutorials when pursuing my learning goals. My explanation is as plain as possible: I merely used to believe that trying to apply a thing that is being studied to your own problems or to compose such a problem yourself for the purpose of learning and attempt to deal with it is a much more efficient approach, because it leaves a lot of room to creativity. At the same time I doubted that dumbly following the track of a tutorial can be that helpful and educating. I don't reject the idea that creativity is a rich source of both skills and knowledge, although now I barely think that the low level of creativity inherent to the process of doing most tutorials makes them bad equipment for learning.
 

Indeed, it is easy to spot a flaw in my reasoning about the educational value of tutorials. Above I have stated that following a track of an exercise dumbly wont teach you much - chances are high that this particular claim is correct. What is false is my belief that the only way to do tutorials is the stupid one, because obviously one can thoroughly inspect every step of the tutorial they struggle with and think over each action that they perform. Such an approach, even though it is guided, turns out to be quite intelligent and efficient. Moreover it seems to me that it is very difficult to do tutorials dumbly - even when I begin an exercise merely following the instructions in it, sooner or latter I find myself noting interesting features and how-to's highlighted by the author and imagining how I could use the tricks in my own solutions. So it's hard for me to accept the fact that I used to yell that tutorials are not proper learning tools.
 
It must have been my eagerness to do my own stuff, to invent things that made me reject other's expertise in the form of tutorials, but there are too many obvious benefits offered by such exercises to ignore them. First of all, their authors try hard to make one's first steps in a field as easy and as efficient as possible, so they tend to focus on the key aspects and to draw one's attention to the most important things of the technology or the tool under study. Therefore a tutorial usually provides one with understanding of the basics underlying all other matters that one is going to deal with. This is notably important whenever you explore something quite complex, incorporating dozens of features and cluttered with details, the purpose of which is hardly understandable from the first glance. Such focusing on the basics, that is delivered by tutorials and saves one from being totally overwhelmed by complexity, is made possible solely by the vast experience of their creators. Beside this, there is one more thing that an experienced teacher can give you through pages of a tutorial, and it has to do with creativity. The problem is that creativity has a dark side - the side that leads one to an invention of complex solutions to simple problems, which were, in fact, solved long ago. Unnecessary complexity of this kind is produced in huge quantities by those who study new fields and, although frequently the students learn the right way to achieve their goals later, sometimes these beasts survive in the minds for quite a long time so that later they can creep, for example, into production code. While I strongly appreciate creativity and acknowledge that even inventing a bicycle once more can be educating and worthy, I still think that when one starts to learn something new, it may be better for them to be shielded from such things by the experience of those who know the proper ways.
 
An interesting thing that I have noticed about doing tutorials while exploring SQL Server Analysis Services with the help of one is that the closer I follow the instructions in the exercise, the more it gives my. I mean that when I carefully inspect each statement that the author makes - even when some points are repeated - I end up with much better understanding, than in case I leap over instructions that seem obvious or too dull to me. Definitely, this has to do with the fact that when I skip something I risk missing minor aspects that turn out to be quite important, and this leads to holes in my mental models. These gaps, in turn, tend to produce mistakes later, but may be hard to spot at the same time. On the other hand, when deliberately examining each sentence in an exercise I focus not on achieving a goal itself, but on learning a good way to achieve it. This frees me from pressure - particularly, from the desire to complete a task as fast as possible and to leap to another problem - and allows me to keep a comfortable pace, stopping whenever I need to explore some aspect more thoroughly or think about a question. Surprisingly, learning how to achieve a goal and achieving a goal are not the same activities.
 
This said, my takeaway from the last tutorial that I have done are not only new skills and knowledge, but the obvious idea that one should not ignore other's experience merely for the sake of doing their own things and producing their own results. Experts always have a lot to say to us - newcomers - and the insights provided by their instructions may be much more valuable than the product of one's creative attempts to master something from scratch. Moreover, such intelligent and wise instructions can even spur a lot of creativity in students because they tend to provoke interesting problems and make one look for the solutions. In the end, you always learn something through facing questions and answering them - tutorials just encourage you to ask the right ones.
 

четверг, 1 ноября 2012 г.

Consciousness Hurts

Being interested in the field of Artificial Intelligence I often stumble upon the question of how to make some artificial system capable of telling good things from bad ones. Because I am much more of a dreamer than of an engineer, multiple supervised learning techniques, which are widely applied and have proven quite useful, offer little satisfaction to me. The problem with them is that supervised learning algorithm merely provides the system being learnt with a set of questions associated with presumably right - or, to be more precise, desired - answers. Afterwards the algorithm does it's best to adjust the system's guts in such a way that it will give the right answers to the right questions - at least, in most cases. As for unsupervised learning techniques, these differ in some way - particularly in terms of applications, although they remain external algorithms trying to adjust the trainee's internals in a manner allowing to minimize the proportion of wrong answers, which are now defined by the algorithm implicitly. While dealing with different machine learning stuff I saw both these approaches producing amazing results, so I am more likely to exaggerate their power, than to underestimate it. Still I never ceased thinking that these fitting activities are too distant from the notion to which the term "learning" originally refers. The information fed to our machine learning tools obviously becomes a subject to some sophisticated memorization and even generalization, nevertheless I can hardly say that it is learnt.
 
This issue was made apparent to me by artificial neural networks. Inspired by nature - the nature of our own brain - many of these models clearly demonstrate how helpless they are until someone arrives with a proper learning algorithm and how much closer they stand to an adjustable screwdriver than to an ant. All the learning is applied to these things from the outside - it is the overseeing mechanism, learning algorithm, that makes adjustments to the system if it performs not well enough during the process of learning. Moreover, this same algorithm is the only thing, that knows what does it mean to "perform well". I know I am not of this kind: I can get beaten by a teacher, alive or not, if I don't learn a lesson properly, but it's not the teacher, who makes me learn it and imprints knowledge into my brain - much greater role is played here by my emotions and feelings: pride or shame, satisfaction or disappointment, relief or pain.
 
Pain is an interesting and off-putting thing. Few if any things in life concentrate our attention so completely and terribly, and few things are more boring to listen to or read about. 
Dan Simmons, The Rise of Endymion 
 
Pain must be the most well known teacher - I mean there is hardly anyone conscious in our world who hasn't ever learnt something through pain - almost everyone has felt it and everyone tries to avoid it. Thus pain is possibly the most comprehensible indicator of doing something wrong and the memories of pain experienced as a result of some actions do their best to prevent one from behaving the same way in future. The high-level mechanism here looks quite simple: pain is not a pleasant sensation at all, so the things which cause it are assessed unpleasant as well. Actually, I believe we can go a little bit further to note that the thing is tangled - although pain is harsh itself, it is also strongly associated with probable injury and is therefore - mainly subconsciously - deemed dangerous and unwelcome. I also feel that it is quite safe to assume that our mind associates the feeling of pain not only with injury, but with the possibility of death as well. Taking this into consideration, there is nothing surprising about the fact that a severe agony may totally absorb one's mind - when death is possible it doesn't seem affordable to let the mind's owner think of something except the ways to escape the least desirable event. An argument supporting the idea of this strong association between pain and death may occur from the fact that an agony itself may be fatal, although I'm afraid that the underlying mechanism of neurogenic shock is too weakly related to the notion of consciousness and so this example is not that applicable here.
 
So pain is a powerful mechanism allowing us to avoid some negative events or, put another way, telling us that certain things are not good, may even appear harmful and therefore should be avoided. It is important enough, but what makes it more interesting is that this mechanism is embedded in the system - our mind and body - in such a way that it is impossible to distinguish it from the entire system itself. We could suggest that multiple sensors spread through our body act as sources of pain - it is true to some extent, but it is not the only case, unless we claim phantom limb syndrome a sort of magic. Furthermore, I believe there is no special 'device' which is fully responsible for sensations of pain in our body - the entire nervous system takes part in producing pain as well as most other feelings, which we experience during our lives. Resulting from the overall behavior of myriad neurons linked together, instead of being produced and managed by some localized distinct components of the nervous system, these strongly related sensations are also linked closely to such activities as motion, vision, thinking, learning. This sole fact allows pain to be quite a successful teacher and makes it so different from all the mechanisms used in machine learning techniques, which I have seen so far. Since it is so overwhelming - in terms of both it's impact on our life and of the degree in which it is interwoven with all activities of our mind - it is impossible to ignore the concept of pain when one wants to ignite a spark of consciousness in something that didn't show it so far.

Maybe, speaking of pain I have made the colors too dark, but the picture is not that grim. All kinds of our feelings and emotions play a vital role when it comes to thinking and learning - to being conscious. I could have chosen anything more appealing - say, pride, satisfaction, love - whatever. It was the relentlessness and severity, with which pain invades our mind, that made me write of it.