Showing posts with label Programming. Show all posts
Showing posts with label Programming. Show all posts

Friday, May 29, 2015

Go, Rust, Python... some random impressions

During a discussion in the Italian Python mailing list, there were some comments about "C-family" languages and how they do compare to Python. As a consequence this post is partly a translation and partly a reworking of the opinions I expressed there. Since it is deprived of much of its context, some parts could not make too much sense; plus I suffer from a severe form of... well I tend to digress. So it might suck rat's ass, but there might be something interest, so bear with me. This post is neither meant to imply any inherent merit in being "similar" to Python nor to criticize any language. As a full disclosure, I love Python and quite like Go. Rust gave me a very good impression, too.

So, Rust syntax shows C influences (even though I feel like the main influence from the C family is actually C++ rather than plain C), but overall the influence of ML is much stronger, especially if we also consider the semantics. Why we ended up comparing Rust with Python is a pretty long story, which started from this post (and evolved here -- in Italian --; thanks Carlos). At the end, other languages were somewhat drawn in the comparison, including Go, C, C++, Java and a C# that is still under shock for having been freed but is alive and kicking nonetheless (my avoidance to make explicit what it is kicking is intentional). Actually, I revisited the thread and we also mentioned OCaml, Haskell, SML (that might have been my fault), Erlang , Elixir, F#, D, Nim (also my fault), Perl, Luc Besson (which is not a programming language, but does awesome movies), Ruby (the programming language), Clojure, Scala, even PHP (oh, well, this blog was supposed to be PG13) and Ceylon (which I did not even know it existed: either I am getting old or people are creating too many languages). Sadly nobody mentioned Lisp or Scheme and surprisingly nobody mentioned Javascript. If your favorite language is not listed here, add a comment. At the end of the day much of the discussion involved Python, Rust, Go, Java and C++, which was to be expected.

Rust and Python

All considered, I feel like Rust is philosophically one of the farthest languages from Python. Again, this is not a bad or a good thing; it is the way things are.

Static vs. Dynamic typing

Python is entirely on the dynamic side of typing. It does not support static typing at all, not even the new Python type hints go in that direction. See especially this paragraph for more context: it is always going to be only runtime checks, and they are meant to be entirely optional (even by convention) even if some linters could make use of it. Even then, hints are meant to be used for duck-typing (i.e., to hint that methods take a Number rather than specifically requiring an int). Most languages in the C family are based on static typing and features in the direction of dynamic typing are incomplete, missing and always frowned upon (think about designing Java interfaces working with Objects instead of  interfaces).

Early binding vs. static binding and dynamic dispatch vs. static dispatch

Again, Python is completely dynamic and late bound. Everything is bound at the latest possible moment. If it were possible to bind calls after the process terminated, Python would do that.
The other languages we are considering here have significantly different approaches among themselves: Java uses dynamic dispatch/late binding; Go uses dynamic dispatch when methods are called on an interface, static dispatch when called on a struct. C++ by default uses early binding and static dispatch. When virtual is explicitly used, dynamic dispatch is used. Rust provides both static and dynamic dispatch: to get dynamic dispatch a trait object has to be used. General consensus (and standard library design) favor static dispatch any time that it is possible and it makes sense.

Object model

The object model of Rust (which technically speaking does not claim to be an OO language, so here we are a bit overloading the term) is remarkably different from classic imperative object oriented languages (e.g., Python, Java, C++). Python, Java and C++ also have massively different object models, but in a sense they are closer to one another than Rust is. On a superficial analysis traits seem to behave somewhat like interfaces, but I feel the differences are really deep. They are much closer to Haskell type classes or ML modules; I do not have much experience with Rust, but designing stuff with Haskell type classes is much different from designing stuff in Java (or Python). Really. As a side note, when trait objects are used, the behavior is rather similar to Go interfaces: the difference is that interfaces in Go are implicitly implemented, while in Rust you have to explicitly implement the traits. Go does not provide anything similar to Rust traits when used statically (there are huge discussions on Go lack of templates and related stuff, won't repeat it here).

Pragmatics

Python philosophy is to keep things as simple as possible and to abstract details away (especially low level details). On the other hand Rust was created with the idea of giving full control over low level details. If you do not want to do it (or do not have to do it) Rust might feel like an overkill. There might be still tons of reasons to use it, though.

Runtime

Python has a very rich runtime. In fact, it is so rich that entirely reasonable optimizations are completely impossible by design (unless you are extremely smart about it: see for example the fact that the function frame is an object and technically you might end up accessing it in some call downstream, so a lot of stack related optimizations are not really feasible -- note to self: check what Pypy guys do about it). Rust, on the other hand, almost does not have a runtime by design. The runtime is really minimal by deliberate choice (which makes a lot of sense considering the target)
Just to wrap it up, if I have to pick a single language in the C family to be closer to Python esthetics, that would be Go (with a honor mention to C itself). Both want to abstract the irrelevant details and the system (in one case the VM, in the other the compiler + runtime) does "the right thing". Both have similar issues when you need to go outside the box and need more control (FFI + cython on one side, unsafe + low level syscalls + FFI in the other).
On the other hand, Rust is much closer to C++ or OCaml: it is a rather complex language that enables fine-grained control on everything; however, it is often hard to relinquish such control: truth to be told, it seems that Rust is much better in this area, the model is simpler to reason with and saner, so the net result is that even if you can exercise a lot of low level control, it is not as painful as in C++, where sometimes the abstractions are all wrong.

Go and Python

Another important similarity between Python and Go is that after few weeks of study (days? depends on how fast one is to learn a programming language) it is feasible to read and understand all the code out there. If something is unclear, it is because of lack of familiarity with the domain (e.g., low level networking details) but not because of complex or unusual language features or magic.
If possible, Go is even more extreme in this than Python itself. There are rather basic concepts in Python that lots of coders do not understand and do not know how to use. Albeit I disagree, lots of people consider hard stuff like meta-classes, the descriptor protocol, coroutines implemented with generators and even the relatively simple decorators.
In Go there are no features with a comparable level of cognitive overhead: the language is extremely simple. You get few basic blocks with very simple semantics that can be composed to obtain arbitrarily complex effect. The net result is that even if the newbie might not know how to compose the blocks to get the intended result, he would still be able to understand a solution created by a more experienced programmer, understand which does what and why. This does not mean that writing in Go is necessarily simpler than writing in Python, just that it might be even simpler to read.

Other languages (such as C++, OCaml, Haskell and perhaps Rust) need much more understanding to be able to cover the same amount of code. To be entirely sincere, I have dabbled with Haskell for about ten years and to this day there is a lot of code that I need to unwrap, disassemble and study line by line to actually understand how it is working (what is doing is somewhat simpler, but appreciating the nuances of the language takes time). With Go I was reading the standard library implementations after 2 days.

In fact, when I fell for Python I was mainly using other stuff (including, stuff like Perl and C++). I used to mock lots of Python tenets. Then a guy I knew from some usenet group asked me to review a couple of his scripts. That was before github, before git was even imagined. It was a world of pain and CVS, and SVN was brand new and people often sent code via email and similar primitive means. But I am digressing... I knew something about networking, back then. Did not know Python at all, though. I was dubious that I could actually help him out; but then I realized I was perfectly capable of reading his code, without having ever written a line of Python, without even having read a tutorial, I might say, without even having read the wikipedia page of Python. Which would be surprising now, but really, back then Wikipedia was probably hosted on something less powerful than my mobile phone (and loading pages took tens of seconds, sometimes minutes). Nonetheless I could read and immediately understand the scripts. I could also easily modify them and make them work. Go is the second language after Python that gave me the same impression, which was the decisive factor in my decision to give it a deeper look.

Rust and Python (again): what about the juniors?

Back to the comparison between Rust and Python... one of the points raised in the discussion was whether some freshly trained junior Python programmer could easily take on Rust. To put a disclaimer here, the archetype of said programmer is not the smart college guy that will end up working for some IT colossus or über-cool startup, nor the garage hacker without formal education but with talent and brains in spades. It is more like the average IT guy, mildly skilled, mildly cable that can, with some supervision, work on a relatively trivial project with other people. To my surprise, there seem to be a lot of these guys. So... the question is, if one of such guys is trained in Python, which language from the C family can he more easily pick up?

The idea I have of Rust, is that it is not the language for him. I believe said novice would probably not even understand where to start. Lots of design decisions in Rust hold that some problems need to be solved in the most efficient way, so the language should provide the programmers control to specify everything to get to that most efficient way. The side effect is that somebody without experience in systems, low level programming and computer architecture would not even know that the choices Rust provides are solutions of real problems. Essentially, I am afraid, a lot of it would be taken just as magic, not as conscious trade offs between control and ease of programming. This is not a critique to Rust: I appreciate a lot its coherence to its goals. Among them there is not "being a language for unskilled novices".

I am also not suggesting that Java would be the best candidate here (really, I have no clues), but to make a comparison to write decent (not excellent, plainly decent) Java it is sufficient to have an adequate understanding of object oriented design. Essentially, it is quite impossible to write sensible Java without a solid understanding of object oriented programming (at least, of the flavor of OOP that Java promotes). And, on the other hand, with a solid understanding of OOP, the resulting Java tends to be fine (again, not perfect, not super smart or super efficient, but fine). The standard library is rich and well documented and there are widely used libraries that cut the few corners that were left (e.g., Guava, Apache Commons, Netty, etc.). So probably somebody with experience in Python and a decent understanding of the OOP part could be able to design in Java software that does not make me want to puke. Too much. And yes, a lot more needs to be taken into account to write great Java code (and the JVM is a tiny operating system on its own that needs to be understood, albeit that might be easier than getting the same level of understanding of the whole Posix model).

I would be very interested in seeing with would happen with Go. Probably the language is too young to spread in those contexts, but I would still love to see whether the transition from Python to Go is really as easy as it seems.

At the end of the day, the impression Rust left me (after a couple of days of study... so I might still be partial) is that of a language closer to a sensible version of C++ that does not drive people to Lovecraftian abysses of insanity and pain. The comparison is a huge stretch: I am talking about feelings here, not objective features. Rust is effectively much more functional than OOP and it does not really work "like C++". It does not at all. Again: the keyword here is "feeling". Also Rust is immensely agile for a language that offers such an astonishing level of detail on low level semantics.

On the other hand, Go seems as perfectly in between C and Python: it can be seen as a modernized C, or like a super-optimized Python. Go get it!



Sunday, August 26, 2012

Does Object-Oriented Programming really suck?

I recently read Armstrong essay on how much OOP sucks. While such opinions would probably have considered pure blasphemy a few years ago, nowadays they are becoming more popular. So, what’s the matter with OOP?

The first thing that comes to mind is that OOP promised too much. As already occurred to other paradigms and ideas (e.g., Logic Programming and Artificial Intelligence) sometimes researchers promise too much and then they (or the industry) cannot stay true to their promises.

OOP promised better modularity, better code-reuse [0], better “whatever”. Problem is, bad developers write crap with every single programming paradigm out there. With crap I really mean “utter crap”. A good programmer using structured programming and a bad programmer using OOP, is that the good programmer’s structured program would probably look “more object oriented” than the other one.

Another problem occurred in the OOP community: OOP “done right” was about message-passing. Information Hiding, Encapsulation and the other buzz-words are a consequence of the approach (because object communicate with messages, their internal state is opaque) not a goal of OOP. Objects are naturally “dynamic” and the way we reason about code is separating concerns in objects having related concerns and having the objects communicate in order to achieve the task at hand. And I’m a bit afraid of talking about “OOP done right”: I really just have my vision. OOP is very under-specified, so that it becomes very hard to criticize (or defend) the approach.

However, OOP becomes “data + functions”. To me data is simply *not* part of OOP. It’s an implementation detail of how objects maintain state. As a consequence, I do not really see data-driven applications as good candidates to OOP. Once again, OOP was sold as universal. It is not. Consider the successes that functional programming is achieving (performance and concept-wise) in this area.

This “data + functions” comes from C++ being the first commercially successful OOP platform. The great advantage was that the transition from structured programming to OOP was quite more easier, that programs could be far more efficient (read “less runtime”) than message passing dynamic OOP systems, at least back in the day. However, there was so much missing from the original idea!

Since back then classes were considered good (and C++ had — and has — no separate concept of interface) + static typing and a relatively verbose language with no introspection, it became rather natural to focus on inheritance. Which later was proved to be a bad strategy. Consequently, there is less experience in building OO software than one would think, considering that for a large part of its history OOP was dominated by sub-optimal practices.

So, what is really bad with OOP? Following Armstrong:

Data structure and functions should not be bound together

Agreed! But I believe that Data Structures are not *truly* part of OOP. Let me explain.

Data is laid out in some way. There is a “physical layout”, for example. You can express that very precisely in C with extensions, to the point of exactly specifying the offsets of the various datas. A phyisical layout also exists in high level languages such as Python. However, it is not part of the Python model. Of course with the struct module or ctypes you can fiddle with it (but they are libraries), however, for the most part, it is just outside the conceptual language used to describe the problem.

Data is also laid out in logical ways. Computer Science defined many data structures and algorithms over them. You can use OOP to express such structures and such algorithms. Still they are not “part” of OOP, they are just expressed in an object oriented way (or maybe not, and remain at structured programming level).

One very good practice in OOP is not using together in the same body of code objects reasoning at different levels. So, for example, you have your low level business logic code that is expressed in terms of data structures, high level business logic expressed in terms of low level business code… and that’s it. Functions is not really together with data. You don’t have “data”. You have some layers of objects.

The point is that OOP is about creating languages at semantic level (you usually do not get to change the syntax). That’s it. If the language is good, well… good. If the language is bad, ok, we’ve got a problem.

Is this suboptimal? In a way, yes. All the indirection may be very expensive. And since abstractions, well… abstract, you may find yourself with a language that is not expressive enough (at least not at the expenses of additional performance costs). Still functions and data structure is not bound together. You just have objects and messages. No “functions” and no “data”. Please notice: this may not be the right thing to do. Still, the problem is not with data + functions, is just related to applying OOP to a specific domain that is ill suited for the approach. OOP is not for every task in the world. But the same objections applies to every system that is built as a stack of layered abstractions.

Please also consider the Qc Na story! Objects are not necessarily opposed to functional programming (you can see them as closures that make different actions available). Objects are just about state + behavior + identity.

Everything has to be an object

And this is something that I don’t think is a problem. It is like saying that Scheme is bad because “everything is a list” (which, strictly speaking, is not true, there are atoms and lambdas etc). If you do not want to program OO, then don’t. If you want, you probably want that everything is an object.

The only issue with everything is an object is, sometimes, performance. From a variety of points of view, such strategy may kill performance. Objects usually have indirections (read pointer). 64 bit pointer for every integer is bad. So we have hybrid stuff like Java and then we have to deal with boxing (either manually in the past or automatically right now). Performance issues can be somewhat “fixed” using proper JIT systems or other optimization techniques. Or providing libraries that do the right thing (see Numpy, for example).

Other than that, I prefer objections in the line of “this thing that should be an object is something else” than those requesting that something that is an object really is. So, I can favorably consider an objection that says that, in Python, “if” is not an object (true). Even though I’m convinced that having if as a statement is not bad either.

And the whole objections with “time”… well, time (and dates) are a bitch to handle. But I find that Python datetime module gets as close to perfection as humanely possible. I really can’t see describing time with a bunch of enums + some structures as an improvement. On the other hand, it looks to me as one of the cases where it is *easy* to see that the OO approach works better.

Consider the related problem of representing a date in a locale. If you introduce a new representation of time, you need either to modify the original function or create a new function (maybe one that works with both things). Creating an interface, on the other hand makes it very easy to introduce new representations of time.

Objection 3 - In an OOPL data type definitions are spread out all over the place


And once again, in OOPL languages data type definitions are not spread out all over the place. They are in C++ and perhaps in Java. And even in Java, if things are done properly, you reason in terms of interface, i.e., in terms of typed messages, not in terms of data structure.
“”“In an OOPL I have to choose some base object in which I will define the ubiquitous data structure, all other objects that want to use this data structure must inherit this object. Suppose now I want to create some “time” object, where does this belong and in which object…
””“
Here it is pretty clear what he has in mind. I think that it is clear that, conceptually, time is an interface and that there is really no need for it to define “data structure”. And if your language is duck typed, the interface is implicit and you have finished before you started.

Objection 4 - Objects have private state


And here I have to agree: state is the root of all evil. Problem is that entirely stateless systems are simply unpractical. We reason in terms of state. We can describe lots of things as stateless, but somewhat we have state. We have files. We have documents. We want them saved and retrieved.
So, we have to deal with state. Agreed. But: 1. imperative programming is about state2. object oriented programming is not, strictly speaking
You can use a “functional” style in OOP, for example. Most of the times, you don’t do it because the code becomes harder to write. But in many situations, on the contrary, you do it because it makes it easier. Often I write immutable objects that cannot be modified: they are not more stateful than a parameter in a function.
Sometimes this is not practical. Ok. And surely using a very stateful style of programming is bad. State in OOP is expressed as behavior. That’s it. Sometimes is done properly, sometimes it is not. Some strategy to deal with state are extremely nice (STM, as an example). Others are not. Also consider how “modern” functional languages really merge concepts from OOP (without being OOP) and how “modern” OOP languages merge concepts from FP (without being functional).
Examples: Python from the OOP side, using generators, lazy evaluation, list comprehensions, closures, etc etc etc.Clojure from the FP side… and all the features it has to express what resembles interfaces, STM and so on…

——

[0] someone once told me they feel lucky when they can actually use the code effectively once, let alone reusing it.

Saturday, November 26, 2011

Good and not so good reasons to learn Java (or any other language)

The first thing to consider is there is really not such a thing like a reason not to learn a programming language. Or better, there is only one reason: lack of time. Learning a language is a tricky business[0]. Learning to use a whole platform is way trickier.

Please notice that some of the reasons presented here are general enough to be applied to any programming language (especially the bad reasons to learn a language). Others are specific of Java (especially the reasons to learn it).

Also keep in mind that the good reasons to learn Java will be presented in another post because this one is becoming far to long to be readable in the few minutes I feel entitled to ask you, dear reader. So believe me... you probably should learn Java. Still, not for the reasons I list here.

Not so good reasons to learn Java

It is widespread

You may be lead to think that since Java is a very widespread language, it is easier to find jobs if you know it. In the case of Java there is the question that I am convinced that it is not a bad thing to know Java and that can have pleasant effect on finding jobs (more on that later), still I would not consider it a good reason to learn Java.

Learning a widespread language means more jobs and more applicants. What really matters is the ratio between jobs demand and offer. And usually for very widespread languages it is not always very high. Skilled, experienced and "famous" developers do not care much. The others should.


It is object oriented

This and the variant "it is more object oriented" are clearly wrong reasons. There is plenty of good object oriented languages. And it is rather hard to decide which is more object oriented (lacking function looks more like a wrong design decision than a clue of being more object oriented).

Besides, I'm not even sure that being more object oriented is good (or bad for what matters). Well... not sure that being object oriented is inherently good either. Maybe in ten years the dominant paradigm will be another. Maybe in two. Three years ago "functional programming" was murmured in closed circles and outsiders trembled in fear at its very sound.

No, joking. Most people thought that "functional" meant having functions (and possibly no objects) and thought about C or Pascal. They did not know it was about not having crappy functions. Yeah, more than that, of course, but that's a start.

It is a good functional language

Just checking if you were reading carefully...
That is a bad reason because it is not true!

It is fast/slow

Oh, come on! Java implementations are decently fast, faster than most other language implementations out there and usually slower than implementations of languages such as C, C++, Fortran. Still, for some specific things it may be nearly as fast or faster. Sometimes it is not only CPU efficiency that matters.

It is good for concurrency

The "classic" Java threading model sucks. It's old. Nowadays other ways of doing concurrency are better (more efficient, easier to use).

Such more efficient easier to use methods are built-in in languages such as Clojure (or Scala, or Erlang). Still, Java is probably the most supported platform in the world. Such concurrency models are not inside the language, but you may have them using libraries.

Sometimes this is a real PITA (language support is a great thing, because the syntax is closer to the semantics).

Moreover having the "older" concurrency model visible may be a problem with the newer stuff. And some other libraries and frameworks may just assume you want to do things the old way.

It is good for the web

Ok... most people do not even know how widespread is Java on the web. In fact, it is. But is it really good? I do not really think so. There is lots of good stuff in Java world for the web, of course. The point is that there is also for other platforms. Here Rails and Django spring to mind.

Moreover, there is loads of extremely cool stuff coming from the functional world. Erlang and Haskell have some terrific platforms. Scala and Clojure also have some extremely interesting stuff and can leverage mature Java stuff but make it really easier to use.

Grails (web framwork) may seem on the same wavelength, still I think there is a very important difference. First, I don't like Groovy at all. In fact Groovy very author says that he would not have created it if he knew Scala. And of course since I do not like Groovy, I do not see why I should be using Grails, which, as far as I know, does not offer killer features over Rails or Django.

Scala and Clojure are different. They are not just "easier" than Java. They teach a different way of thinking and approaching development. And of couse, they are great for the web also.

I already know it a bit

This is quite an interesting point. Why, if you know a language a little, shouldn't you learn it well? This essentially makes it clear the difference between a not so good reason to learn something and a reason not to learn it.

The point is simple: if you are interested in learning Java (for the good reasons), do it. But from knowing a language "a bit" and knowing it "well" there is quite the same distance that from not knowing it and knowing it well. So don't do it because you think your prior experience may be extremely relevant.

There are languages which are just easier to master (where easier means that to learn them from scratch it takes less time that to become as proficient in Java as you would learning those languages -- yeah, Python, Ruby, [1]...)

Besides, I feel that the second step in Java is rather hard. I think that Java is very easy to learn for experienced developers (it is not a joke). The very first step in Java is relatively easy (a part from lots of useless crap like having to declare a class to put there a static main method and overly complicated streams). The step just after that, the one that takes you from writing elementary useless programs to writing elementary useful programs is quite harder, since lots of useful stuff in Java requires to understand OO principles quite well to be used without too many surprises.

So, you may have learnt Java at the college. Still, if you to hack something and "grow" there are languages that let you do it faster (Python or Ruby).

I have to

This is the worse possible reason because I don't see the spark in it. You have to. You probably are an experienced developers that got bored to death reading this post, you did not know Java and you have to learn it. Maybe your boss wants you to do some Java.

I'm sorry for you. Java is not easy to learn (especially to learn it at the point you can work with it). Mostly because everybody has been doing Java in the last fifteen years. Smart people and dumb people.

As a consequence there are truly spectacular pieces of software it is a pleasure to work with and worthless pieces of over-engineered crap. I truly hope that you are going to work with the great stuff.

Still I consider a "bad reason" to learn a language, because you are probably not going to enjoy the learning process. If you were, you would have used a different sentece... like "I want to".
And perhaps the thing would have been "My boss gives me the opportunity to increase my professional assets learning this new technology, Java".

It was easier to download/find a book/my cousin knows it.

One of the most popular reasons people pick up a language is because they find it installed on their system. That is not usually the case for Java, in the sense that usually the JDK is not installed, even if the VM is. Variants of this argument are a friend proficient in the language or, more frequently, availability of books at the local bookstores. This kind of arguments usually apply for complete programming beginners or people who had prior experience that has not been refreshed for years (e.g., old Basic programmers).

It is true that it is easy to find manuals for Java. The point is that not every manual is a good starting point. Specifically, this kind of user really needs a manual targeted at a true beginner. Unfortunately, that is the category of books where more craps piles. First beginners usually do not really understand if the manual they are using is crap.

If their learning process is too slow (supposed that they see it) they usually blame: (i) programming in general, (ii) the language or, worse (iii) themselves. Well, the language may have its faults (especially in the case of Java, still C++ is worse for a beginner), but it is important to understand that the culprit is usually the manual.

The funny thing is that the people who know how to tell apart a good manual from a bad one are usually the ones that do not really need an introductory book, are probably not going to buy it and more often than not are not enquired about a good manual. So unless their cousin is actually a skilled programmer, beginners are really risking buying a bad manual. And please, notice that even skilled programmers are susceptible to a similar argument: if you are interested in a new language and you read a terrible manual you may build a wrong opinion on the language (and perhaps you are not interested in spending another 40 gold coins to convince you that the language is really bad).

So please: read reviews on Amazon (or similar site -- notice that I'm not going to suggest to buy from Amazon, even if I often do and I am an associate -- here I'm just saying that the amount of reviews on books on Amazon -- com -- is usually large enough to make an idea). Find people that are experts and have teaching experience: their reviews are probably the one to base the judgment upon. Then buy the book from whatever source you want.

So do not buy a Java book because you can find books on Java and not on Clojure[2]/Python/Ruby/Whatever[3]. Choose the language (this is hard), choose the book (this is easier), buy, read it, study it, code code code.


---
  1. I kind of found out that for people really convinced that learning a language is easy one or more of the following apply:
    1. have an unmotivatedly high opinion of their knowledge of the languages they claim to have learned
    2. have a very inclusive definition of "learning" a language (e.g., writing hello world in it)
    3. only know and learn very similar languages (really, if you know C++ it's gonna be relatively easy to pick up Java, if you know Scheme probably Clojure is not going to be a big deal, etc.)
    4. and tend to write "blurb" in every language they know (so for example they write Python as if it was C++ -- and usually are very surprised when things go badly)
    Of course there is also a lucky minority for which learning languages is very easy.
  2. It is funny that I am suggesting to learn only "object oriented dynamic" languages such as Python or Ruby. I understand that many advocates of functional programming may disagree. But I somewhat think that while some kind of people greatly benefit from learning a functional language as the first language they truly master, many others are just not fit for functional programming because it is too abstract and working imperatively may be easier for them. As a consequence, languages such as Python or Ruby may naturally lead you towards functional programming if it is the kind of stuff you like, but are still usable if you are not that kind of person. I have seen things...
    And yes... if you are the kind of person that likes functional programming, you will get into functional programming sooner or later. This is just a bit later. 
  3. Notice Clojure here...
  4. Whatever is not a language. Still, it would be a great name for a language. Maybe not.

Friday, November 25, 2011

Brief morning delusion…

So basically it's a couple of days I try to use a given piece of software. And for some unfathomable reason the thing does not do a specific thing which I need and it shall do. I'm not going to be more specific, because I do not want to point fingers.

The point is that it does not give any clues on why it is failing or what is exactly trying to do (thus "how" it is failing). Since the project is open source I decided to take the sources and hack my way to the problem. I have a rather good understanding on the domain that I'm going to solve (it's related to unix processes -- though in OS X environment --). I'm familiar with both.

It's been a while since I last wrote some Objective-C, but this time I should just look at the sources, perhaps set a couple of breakpoints and find out what is happening. My plan is that after that I could fix the source so that:

  1. It logs what is doing
  2. It logs any errors that occur
  3. Perhaps I fix the specific problem in the code

The first bad piece of new is that I spot some obvious software engineering mistakes in the software. Unfortunately it is stuff that needs more than a casual hacking to be fixed (specifically, I'm talking about configuration stuff hardcoded in the sources). Still, it is not probably a big issue. It may even make sense in some contexts… well, not really. But anyway. Some wrong data-structures… but everything is basically fine: the code, a part from that, is well written and rather clear.

So I localize the piece of code that fails. It does a bloody lot of magic, in my opinion. My guts tell me that some of it is just unnecessary, some better design could lead to much simpler and less magic code. The only problem is that sometimes such kludges are just a consequence of unorthogonal design of the underlying systems (in this case OS X). But that is something I would do later on, after simply adding the code that logs possible errors (I think that is of paramount importance for the semi-technical users of the software, in the sense that they could better understand what is going awry when they customize it).

Then a thought strikes me: compile the whole thing just before making modifications. The svn repo should have been left in compilable state… but no. It is not. So I should have to find the last point where it can be compiled. Which I could do… well, another time.


Saturday, November 5, 2011

Scheme Rant, but a bright future lies ahead!

Is this the best moment for scheme ever? First of all, I have to admit that although I quite studied the language in the last years, I'm a bit outside the spirit of the community. My impressions basically come from some discussions I have with more experienced schemer friends and reading stuff on the web.

I have often wrote about my difficulties with finding a satisfactory scheme environment. Essentially problems boil down to two things:

  1. I have very high expectations for programming environment (batteries included, etc.)
  2. I have very high expectations on Lisps, probably because every lisper I met spent a great deal of his non-programming time saying how lisp did this and that 30 years ago. So I expect to do it now, to do it fast and to do it well.

The issue with the first point is that I'm mostly used to programming environments which are mostly unique. Python is just Python. I use the very same interpreter everywhere, I know what it does, which features are supported and which ones are not. Same thing for Clojure or Erlang and nowadays mostly true for Haskell. I avoided the issue with C/C++ being rigorously adherent to the standards or to very minor gcc extensions (and by the way, I'm always using gcc).

On the other hand in Scheme there are many implementations and there is not a clear winner. There are implementations which are "worse" than others, but among the good ones it is hard to choose.

The problem is especially significant because I somewhat got into a period of transition between R5RS and R6RS (which are two standards). I somewhat lived something like that with C++, but at least back then there was clear consensus that pre-standard C++ was unarguably worse than post-standard C++.

Essentially, R6RS came out in the mid 2007 and I started learning Scheme not much afterwards. Moreover (here discussions with my friend kick in) a large part of the Scheme community did not like the standard, feeling that the new language was too large (plus strictly speaking it should lack a REPL).

After reading Dybvig's 4th edition Scheme book I found that mostly I like the new stuff. However, for some unfathomable reason I sticked with Gambit, which does not implement it. So, while I like the idea that Scheme was a little language which you use to build your language, I missed lots of R6RS features which just make to much sense for me (as an "application/library" programmer, instead than a "language programmer"). That, and the fact that if you want to toy with a new language, you do not want to re-implement merge-sort (unless you are are explicitly choosing that example to learn the language).

So there were lots of incomprehensions between me and the my scheme learning process. But I'm afraid that lots of non schemer may be feeling the very same stuff about it (and perhaps jump to clojure, which fixes that issues by default).

Some time later, the other scheme implementation I used became another language (and I'm looking forward to read the new No Starch Press book about it). So the more "batteries included" tool I knew in scheme, ceased to be scheme.

I think things would have been easier if I just picked up an R6RS implementation and stick with it. However, things went differently. Not that I do not like Racket, still I find ackward to code in Racket when I want to grok Scheme and code written in Racket is so often not very portable to other schemes (and that is why Racket is Racket). I'm not complaining about Racket: I think they did the right thing.

However, nowadays the Racket book is scheduled. And there is going to be a new standard R7RS. And they decided to define a "small" and a "big" language. And I really do believe this is wonderful because it would make clear exactly what to expect from either language also to newbies. Moreover, perhaps some of the more reasonable improvements with less reaching implication which R6RS will be added to the small language as well. E.g., how to define libraries and generic information about what to expect from the "platform". Or maybe "values" related stuff.

So, in essence, I think in the future it will be a great time to learn scheme. Maybe I will have the courage (and the time) to re-learn it from scratch and fix all my miscomprehensions which came from the way I originally learned the language.

Tuesday, August 30, 2011

Clojure: Unfold and Anamorphisms

I found rather unusual the apparent lack of an unfold function in Clojure. Perhaps I was not able to find it in the documentation (which I hope is the case), in this case, happy to have done a useful, although slightly dull, exercise. However, if this is not the case, here it is unfold in Clojure.

Unfold is a very powerful function which abstracts the idea of generators. I tried to make it lazy (please, feel free to break it) as we expect from clojure functions. Some examples are provided:

Here, we simply generate a list of elements from 1 to 11 (included):

(unfold #(= 0 %) identity dec 10)

The following example is more interesting: here I show that unfold is at least as powerful as iterate. It is possible to regard iterate as a simpler (and easier to use) variant of unfold.

(unfold
      (fn [x] false)
      identity
      inc
      0)

The code is equivalent to (iterate inc 0). Now the idea is that:

  1. The first argument is a predicate that receives the seed (last argument). If it is true, the function returns the empty list; otherwise returns the cons where the first cell if the second argument applied to the seed and the second cell is a recursive call to unfold. All the arguments but the last one are the same. The last argument becomes g applied to seed.
  2. The second argument is a unary function which is applied to each individual "value" which is generated. In this case we just use identity, because we want just the plain arguments. We could have used #(* %1 %1) to have the squares of the values or whatever makes sense.
  3. The third argument is the function that generates the next seed given the current one
  4. The last argument is just the seed

Unfold is extremely general. For example the classic function "tails" that returns all the non-empty sublists of a list ((1 2 3) -> ((1 2 3) (2 3) (3))) could be implemented as:

(defn unfold-tails [s]
  (unfold empty? identity rest s))
  

The standard map, becomes:

(defn unfold-map [f s]
  (unfold empty? #(f (first %)) rest s))

Now, here the implementation:

Upgrade

With this update, I present an upgraded version with the tail-g parameter. The classic srfi-1 Scheme version also has this argument. Originally, I thought that in Clojure I could use it to have different sequence types. On a second thought, it can't work because of the way Clojure manages sequences (at least, not in the sense that I intended it).

On a second thought, however, it is highly useful. Consider for example the tails function I defined above. In fact it returns all non-empty substrings of a given string. This makes sense in lots of contexts, however it is different from the definition of the Haskell function.

tails                   :: [a] -> [[a]]
tails xs                =  xs : case xs of
                                  []      -> []
                                  _ : xs' -> tails xs'
That could be roughly translated (losing laziness) as:
(defn tails [xs]
  (cons xs
        (when (seq xs)
          (tails (rest xs)))))

This function also returns the empty list. Surely, it would be possible to (cons () (unfold-tails xs)), but that would break the property that each (finite) element in the resulting sequence is longer than the following. Without the kludge, the unfold-tails function breaks the strictness property that the resulting list contains xs if xs is empty. Accordingly, tails should really be defined to include the empty list. Appending the empty list to the end of the list returned from unfold-tails would maintain both properties, but would be excessively inefficient.

On the other hand, specifying the tail-g function allows a cleaner definition

(defn tails [xs]
  (unfold empty? identity rest xs
          #(list %)))

Essentially, without tail-g it is not possible to include in the resulting sequence elements which depend from the element which makes the sequence stop.

Updates

  • Substituted (if ($1) (list) ($2)) with (when-not ($1) ($2)) after some tweets from @jneira and @hiredman_.
  • Added version with tail-g
  • Here more functions and experiments with unfold.
  • Removed HTML crap from cut and paste

Technorati Tags: , , , ,

Friday, March 18, 2011

Clojure Macros: Wanted and Unwanted Symbol Capture

Introduction

No language is perfect. Most languages do not even come close. Some give the programmer the tools to bridge the gap between his idea of perfection and the actual language. Most of these tools are under the "meta-programming" umbrella, and macros are probably the more powerful.

Macros are dangerous tools, indeed. Most articles, books and posts on macros mention somewhere not to abuse macro usage, because they make the programs less clear (TODO: mention somewhere not to abuse macro usage; DONE).

One of the trickiest part is avoiding unwanted symbol capture. Here the the Lisp world divides: some think that symbol capture should be syntactically banned (or something like that), some believe that it is the programme's task to avoid unwanted symbol capture (with the underlying assumption that wanted symbol capture is fine).

I’m not usually happy when a feature is designed not to let me do something; well, quite depending from that something, indeed. I’m not at all concerned with higher level languages not letting me hacking directly with memory; but as far as I can tell, that is more a question of “language model” than of “designing a language not to let me do something”. In fact, Hoyte goes a long way in motivating that symbol capture is not a bug, but a feature.

I believe he is right. I do not find it hard to find situations where I want symbol capture to occur. Of course, in his book, he gives plenty of examples about that; but that’s not the point. I am limiting this to the context of clojure.

Clojure

First, clojure has a nice feature to make it easier to avoid unwanted symbol capture without actually forbidding that. I’m talking about placing a # at the end of an identifier to gensym a new symbol. This is nice to have, nice to use, nice to understand and solves brilliantly the common cases.

But the point is wanted symbol capture. In Clojure we have an interesting example: when creating a gen-class, the methods take an additional explicit parameter “this” (a-la Python, %s/this/self/gi). Like in Python, this is a conventional name, and could be called in any way; still, since Java has an implicit this reference to the current object, it is nice to maintain the convention.

This is a typical example when we explicitly introduce names. It is easy to understand, easy to write and that’s it. There is no capture involved, as we are simply using a function parameter. On the other hand, code in the proxy macro refers to the proxy being created with “this”.

It can’t be changed (as far as I know) since it is injected by the macro. In fact this is wanted symbol capture. The authors want to capture the symbol “this” in the client code and give it meaning. Looking at the code of proxy, it is rather explict.

Both approaches have their advantages: explicit this is easier to understand. Knowledge about how functions work is sufficient to read (and write the code). On the other hand, the proxy macro requires us to know something more: in fact it is a true “new” piece of language. If we did not know that proxy “magically” injects this, we could not understand/write the code. Probably we would say that the code is wrong as “this” should not be bound to anything there.

On the other hand, auto-this in proxies somewhat reduces code clutter. I have not a distinct opinion whether it is a good thing or if I’d prefer explicit this. Perhaps I’d prefer uniformity: that is to say, explicit this everywhere or implicit this everywhere. Though, that part of clojure is under active design and development.

Back to my macro abuses

This post started because of a macro I wrote. The first version is “clean” as it does not capture anything.

(defmacro let-attributes* [this state-method attrs & body]
`(let [{:keys ~attrs} @(. ~this ~state-method)] ~@body))

which would be used as:

(defn exp [this foo]
(let-attributes* this state [bar baz]
(println bar baz foo)))

Essentially it just wraps let+keys to provide a slightly terser syntax (in fact, I find it boring to use .state all the time; I have to say that it is one of the parts of clojure I like less). However, it is still rather verbose. I have to manually specify things I would rather not; in fact, in my code this is always called this and state is always called state, as I did not find a better name.

So I wrote a second version of the macro. A version that is not clean.

(defmacro let-attributes [attrs & body]
(apply list 'let-attributes* 'this 'state attrs body))

And the usage would be just:

(defn exp [this foo]
(let-attributes [bar baz]
(println bar baz foo)))

In fact, is very dirty as it depends from the context where is expanded (which makes it different from the proxy macro). It assumes that “this” is bound to an object with a .state method. But is faster to write.

Here we can also spot the other problem with macros. I believe that every clojure developer is tempted to write his own macros. Macros that may solve similar purposes, but are inherently different. Code using such macros looks different at a different level than code which just uses different libraries and functions.

This is essentially the problem with fiddling with the language itself, which is what happens when we are writing a macro. Basically every time we write and use a macro, we are developing (inc clojure).

Amazon associates

"Let Over Lambda" (Doug Hoyte)

Technorati Tags: , ,



Wednesday, March 16, 2011

Clojure: great exploratory programming

One of the major drawbacks of Java development is the almost complete lack of a REPL. All my favourite languages have a REPL, and I have to say that I've grown completely accustomed to it. I almost can't do without. The possibility to explore new libraries step by step, perhaps with some features common on IDEs (and some more, as it's runtime), is invaluable.

It is hard to understand how the lack of a REPL cripples the learning process of the language (in the first place) and of libraries without having extensively used a REPL. Java somehow compensate this with extensive IDE support; it is possible to write many small programs to try the libraries. However, it is a poor substitute for a true REPL. Java has some third party REPLs, but I did not feel at ease with them. Rather, I resorted on unit testing (which has the added benefit of documenting what you are doing).

Recently, I found out that clojure is an excellent candidate to do the kind of exploratory programming Java is not good at. Essentially, I find it easier to try the libraries with clojure than with Java. My usual setup is Emacs + swank. The close integration between the buffer and the REPL, means that I'm free to try wrapper functions in the REPL (most Java APIs are quite over-engineered) then move them to the editor and use them to build further abstractions.

For example, here I just wrapped the API to build ER and BA random graphs. It is quick to use in the REPL and then I can easily study the generated graphs directly in the REPL. Notice that this is different from both using unit testing and small programs; in both situations, should I want to run further functions on a graph, I would have to save it to disk and reload it, because a successive run on the program would yield a different graph.

This is another example:

Here I created a bunch of functions to visually present a graph. Essentially I can easily use the macro to experiment with different layouts (that is to say the algorithm placing the nodes. Notice that once again, running things step by step, means that I can try new things on the same graph and compare them.

And these were added at the end to check some standard metrics:

I have to admit that I somewhat missed Common Lisp loop, for the latter.

Technorati Tags: , , ,,

Monday, March 14, 2011

Clojure & Leiningen in a Java Project

These days I've been busy with other stuff to do (including but not limited to some logistics and fixing a couple of papers); however, these morning, I picked up my good old mixed java/clojure project to port it with leiningen.I'm pretty happy with IntelliJ build system, but I needed some more flexibility.

There are some interesting issues with the project:

  1. it has both Java and Clojure files
  2. it has both aot and regular Clojure files
  3. it depends from lots of different Jars
  4. it depends from a "private library" which has not been (and cannot be) placed under publicly available repositories right now

The idea is that: Java code does not explicitly depend on clojure anymore. It is safe to compile Java before clojure; this is a major upgrade from (this and this and made things easier). On the other hand I have some "utility" clojure files which have not to explicitly aot compiled, but are required by the aot compiled sources. And I have some wrapper clojure scripts which depend on everything and is also compiled but it does not give any trouble, apparently.

To give an idea, this is the project:

I also installed lein-javac, even though I'm not sure it is needed. However, I can compile the Java code and that is fine. That was the easy part. And using "official" jar files was also easy Moreover, since interactions from aot compiled and regular clojure code did not give me any issues that is the state:

  1. it has both Java and Clojure files
  2. it has both aot and regular Clojure files
  3. it depends from lots of different Jars
  4. it depends from a "private library" which has not been (and cannot be) placed under publicly available repositories right now

Unfortunately, it seems to me that lein does not detect dependencies among aot compiled sources. That is to say, I have to manually order the files in the correct order. I hope that I just missed some lein feature, because this is just crazy. My first idea was that I could compile all the namespaces that contained all aot compiled code (I structured the project because of that). Apparently, it works; however if the compilation does not take place in the correct order, it does not work. Please, someone tell me I'm wrong. Someone told me and today with a simpler regular expression everything worked. Basically I'm not excluding files which are not required to be aot compiled from aot compilation. I just compile everything and that's it.

The only "problem" was dependency from a proprietary jar file we are developing. I found out that simply placing it in the deps was not enough as the directory is often removed and recreated. I put up a kludge:

Now everything kind of works. I'm not sure it's an improvement over the IntelliJ variant. I can use Emacs+swank (which is good), but I have a tricky "manual" step if I want to add aot compiled files. Besides, since there is a lot of setup to run the project, I'm not even sure that swank is a huge advantage, as I could only use it to run snippets "outside" the core.

So essentially it boils down to whether I prefer Emacs or IntelliJ for this project. Being heavily framework based, exploratory programming is rather hard to do, which removes some appeal from slime, as LaClojure REPL is adequate. Moreover, I prefer IntelliJ on pure Java (and I have some) and it seems to better autocomplete Java stuff (e.g., in the :import sections). So perhaps the trick is IntelliJ+LaClojure+Leiningen.

Technorati Tags:,, ,

Sunday, February 27, 2011

call/cc! I yield! Bah, lazy-seq.

Modifications

  1. 03-01-2011: an additional scheme variant has been added.
  2. 03-02-2011: minor modifications

Imperative Iterators

The archetypal implementation of imperative iterators are Java's subclasses of the Iterator interface. Everyone who has ever implemented an Iterator should acknowledge the pain that is to explicitly maintain state between calls. The essential problem is that the state has to be explicitly maintained. In particular there are two different concepts of state: the iterator "low level" state and the iteration state. In the iterator we have to save "what we have done" and explicitly do so.

Then every time next() or hasNext() are called, we have to do some more computation, save our internal state and return the control explicitly to our caller. This is the implementation of a Javish iterator in clojure to pre-order visit a tree like structure.

I decided to use Clojure instead of Java because the example structure was easier to write, the example could be just run in a REPL, i prefer Clojure to Java and other irrelevant details. However, that is essentially the way iterators are written in object oriented languages (included nice ones, like Python) or languages that have to interoperate with OO languages.

The principal issues is that the "value returning" logic has to be mingled with the iterator bookkeeping logic; I believe that this (although more versatile) is far more complicated that simply adding all the values in a (flat) structure and returning the structure. The reason why this is not the dominant approach is that it is less flexible and less efficient. We also notice that iterators can be infinite.

Ah, if you like OO mumbo-jumbo, this is of course the Iterator pattern.

Yield Statement

In this section we briefly examine Python yield statement. The corresponding yield expression (which essentially allows limited coroutines) is not considered here. If a Python function contains the yield keyword, its semantic changes.

The yield statement may only be used inside functions. A function that contains a yield statement is called a generator function.

[...]

When a generator function is called, the actual arguments are bound to function-local formal argument names in the usual way, but no code in the body of the function is executed. Instead a generator-iterator object is returned; this conforms to the iterator protocol[6], so in particular can be used in for-loops in a natural way.

[...]

Each time the .next() method of a generator-iterator is invoked, the code in the body of the generator-function is executed until a yield or return statement (see below) is encountered, or until the end of the body is reached.

If a yield statement is encountered, the state of the function is frozen, and the value of expression_list is returned to .next()'s caller. By "frozen" we mean that all local state is retained, including the current bindings of local variables, the instruction pointer, and the internal evaluation stack: enough information is saved so that the next time .next() is invoked, the function can proceed exactly as if the yield statement were just another external call.

Essentially instead of using the structure of the previous example, we could have written:

The idea is that when yield is encountered the generator "returns" the value and when next() is subsequently invoked, it resumes execution from that spot. Essentially it is a neat syntactical facility that "saves" somewhere the continuation of the computation in order to be ready to resume from that point.

call/cc

Ok. I said it. Continuation. That is. When the goal is to experiment with control, scheme comes to mind. This is our representation of the tree:

Now I provide an easy example for doing the preorder "functionally". The idea is to provide a function taking a callback function that is called on each element of the tree when is visited. The function also returns the return values of the callback function (in reverse order); anyway, use display as a callback function and we have our familiar example:

Here we did not use continuations. In the following example, I use continuations to save the state of the computation where to resume (after the yield) and to jump out (simulating python yield statement) with another continuation. I think this code should be written more clearly; I'm no expert with continuations (I usually find them too general), so suggestions are welcome.

When generator is called, it returns a function that every time that is called returns the successive element. It behaves like an almost like an iterator and has the structure of Python generator functions. Boilerplate code could be avoided using a macro. Essentially, lines 2-6 are boilerplate and similarly are lines 11-13 and 18. The difference with a "java like" iterator is that when there is no way to test if there is a "next" element (no hasNext method, closures are objects with just one method! ;)) and it returns null forever. So the implicit protocol is "do not put null objects in the tree" and if you get null, then you are done. Other strategies could be used, e.g., instead of returning the value, a list containing the value is returned; empty list means that the iteration completed; list containing an empty list meant that the tree contained a null valued node.

We could have used continuation passing style and that would perhaps have been more similar (even though far more complex to understand and write) than the corresponding Clojure way to do it.

In the comments a different scheme continuation based solution has been suggested by Marco. It is far more elegant than my original solution, but is structurally different from the Python code of the sections above. In fact, it does not use an explicit stack to manage the tree traversal (as both the Python version and my scheme version do), but relies on more recursive calls, essentially using the implicit call stack.

What about lazy-seq?

Essentially we specified that iterators are needed because they are efficient and more general. Indeed in languages such as Python, generators are an easier way to write iterators. What about Clojure? The example at the very beginning is not the way things should be done in Clojure. On the other hand the iterator in scheme would be a good API for Clojure as well. In fact it is the very same pattern that we have with map (Haskell programmers here may want to extend the discussion on "mapping" on different algebraic types, such as Trees, and not only Lists).

Imperative iterators are just a design pattern. Call/cc is a feature so powerful you can use it build almost every other control abstraction. Yield can be used to implement coroutines (which could also be implemented with call/cc). And what about lazy-seq? Lazy-seq is nothing like that. Many Lisp books show a simple example where macros are used to implement lazy evaluation (or at least list lazy evaluation, which is what matters most). lazy-seq basically does that. The difference is that other Lisps are usually not built around the idea of lazy sequences the way clojure is.

Consequently, most libraries and APIs do not work (or do not play well) with lazy evaluation. They could be made to work, but it is simply outside Common Lisp pragmatics, and there is not anything wrong with that.

But back to lazy-seq... why it is here? Consider the code below.

I want to point out the structure of the tree-seq-aux function. Indeed, the function is not recursive. It simply return a lazy-seq. The second parameter of the cons cell we put in the lazy-seq, is another call to tree-seq-aux. However, this is not recursion: when lazy-seq is evaluated, we already returned from the previous call.

But thing are even more interesting than this. That cons cell is somewhat similar to the iterator we saw at the very beginning. Its car (first) is the current value. It is "next". And what is its cdr (rest)? Suppose that our iterator was not stateful. Suppose instead that calling next() returns both the current value and another iterator which will provide the successive values. Well... basically it is our cons cell.

Pair<Value, ConstIterator<Value>> v = it.next();

That is essentially our cons cell. But in fact we somehow have the same pattern we had in the continuation based example. We "save" what to do next (akin to the jump continuation) in the cdr of the cons cell; however, we do not have to evaluate it right now because of the lazy-eval. As I already said, if we used continuation passing style, we would have placed the recursive call which we put in the lazy-seq cons cdr in the explicit continuation.

The advantage is that it is easier to write, easier to understand, easier to use. Unfortunately is much more specific and powerful. But I don't mind practical tools, when they have the elegance lazy-seq has.

Technorati Tags: , ,, ,



Thursday, February 24, 2011

Clojure: generating sentences given a grammar (part 1)

Introduction

The idea of this post is writing a simple sentence generator based on a given (context free) grammar. The idea and the program come from PAIP.

However, this is essentially only a starting point to present some philosophical differences of programming languages and to show how Clojure is every bit as versatile as Common Lisp (on this kind of problems) and perhaps some new built-in features make it an even more interesting choice for this problem.

Description of the problem

The problem here is simple: generate sentences in a small subset of english. Small is an overstatement; perhaps diminutive is more appropriate. At the cost of looking snob, I believe that many mainstream programmers may already be cowling in fear or, if skilled enough, they are looking for a library doing the task.

There is nothing wrong with the library approach. Moreover, it is probably the right choice for a "real" project, because it is certainly more versatile than the few lines we are going to write, more tested [well, actually not, as our piece of code is just a very close translation of some 25 years old program or so ]. Perhaps, more efficient.

Now suppose we are not meant to use external libraries. Unrealistic as it may be, consider that we want to learn something beside a new bunch of API's. Or maybe we just want a lightweight solution. Different approaches are favored for different programming languages.

I jump the stage in which the programmer writes a program specifically translating the example grammar. I believe it is clear from the specs that we want to generate sentences for every grammar with some prerequisites. As a consequence, we have just to represent the grammar in some format, read it, understand it and generate the sentences.

This is essentially a language interpretation problem: we have a "program" written in a given language (the grammar) and we want to interpret it (and generate the sentence). The language is not turing-complete, of course [ as we have decided that it should be a CFG, and consequently out interpreter machine needs not to be a turing machine ]. I read somewhere (which I mistakenly believed to be the Unix Programming Environment) that the authors strongly advised to transform generic problems into language interpretation problems. The idea is that this way it is easier to think at a higher level of abstraction (the one given by the interpreted language, not C); efficiency is probably not an issue either, as a specific domain language is likely to be more efficient than a general purpose interpreted language.

An example grammar is:

Sentence : Noun-Phrase + Verb-Phrase
Noun-Phrase : Article + Noun
Verb-Phrase : Verb + Noun-Phrase
Article : the, a, ...
Noun : man, ball, woman, table, ...
Verb : hit, took, saw, liked, ...

Implementation details

The question is how to represent the grammar. I believe that most Java programmers here are already mentally checking the API's of their favourite XML parser. Although in Clojure there are more appropriate data structures, I chose to represent it as a list of lists, as with the Lisp tradition. This has the advantage that examples prepared for PAIP program can be directly used without modification. Moreover, the size of the grammar is modest enough not to make it an issue. Besides, switching from lists to vectors does not lead to changes in the code.

I belive that here we can spot a very evident cultural difference. In the Java/OOP world the problem is two-fold. First we have to translate the grammar in data structures manipulable by Java, then we have to actually interpret them. The first part is essentially the task of a parser: for XML there are many parsers (but the syntax becomes very clumsy, IMHO); on the other hand it is possible to use parser generators such as Yacc or Antlr. The second part is also considered a serious issue: in fact in GOF's Design Patterns the Interpreter pattern exactly deals with these kind of problem. Their suggestion is to create a class for every terminal and non terminal. Please notice that we are not talking about the terminals and non terminals of the grammar (such as Sentence or Noun Phrase) but about the terminals and non terminals of the language in which we express the grammar (such as Rule, Alternative, Head and things like that).

It is interesting to notice how both problems in Lisp are non-problems. Lists and atoms provide a nice representation for the grammar itself:

(def *simple-grammar*
'((sentence -> (noun-phrase verb-phrase))
(noun-phrase -> (Article Noun))
(verb-phrase -> (Verb noun-phrase))
(Article -> the a)
(Noun -> ball man woman table)
(Verb -> hit took saw liked)))

The parser problem is completely solved. And the interpreter pattern just becomes some list processing. Less fancy for sure, far easier nonetheless. It is just list processing. Nothing more, nothing less.

This is essentially a straightforward translation of the original common-lisp variant. However, does the job. You can also experiment with different grammars.

Morale

Notice that here the key was not lisp homoiconicity. It is a feature which has not been used. A similar approach could have been used also in C (though grammars would have been impossible to share, unless a specific file based representation was developed). Here the key was just thinking in terms of a DSL.

This is not obvious. Some times ago I developed a toy Prolog program which generates imprecations and similar stuff. Unfortunately enough, this example did not immediately come to my mind. Instead I assumed that Prolog ease of development would be (alone) a time safer. It was not. Using the full language power to generate sentences proved to be far more complex than switching to a simple grammar representation + a bunch of procedures.

Future work

Right now, I'm trying to adapt the generate-all to Clojure. Indeed, although the straightforward translation actually works, it is not lazy, and consequently cannot be used with grammars generating an infinite language.

Books & Amazon associates

"Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp" (Peter Norvig)

"Unix Programming Environment (Prentice-Hall Software Series)" (Brian W. Kernighan, Rob Pike)

Technorati Tags: , ,


Wednesday, February 23, 2011

Is laziness tiring? (part 1)

Introduction

A part from the obvious pun, I effectively consider laziness tiring. Let me explain: I'm quite used to lazy languages, I've used Haskell for some time, and I use effectively Python lazy features extensively. Moreover, it has been a while I use clojure as well.

Laziness is an extremely powerful conceptual abstraction: it is like having an optimizing compiler tailored at not perusing your sequences, so that you can write code in a natural way. I would definitely want some form of laziness (especially sequence based laziness) in every language high level enough that it does not hurt.

Moreover, lazy sequences do not have many of the usual drawbacks of default lazyness. First, you somehow have to explicitly require it; when not required is there but it does not hurt either (e.g., map). It does not play extremely well with side-effects, but in clojure the language is designed to limit the scope of expressions with side effect.

However, I decided to understand more fully how lazyness works in clojure.

lazy-seq and cons

This is a simple code I wrote to test multiple functions on a given collection and see how they fare.

Essentially benchmark is a macro taking a vector of functions. It builds code fragments which call the desired function on the list and prints the time needed. We use a slow-computation function which sleeps for 500 ms. This way is easy to count calls to slow-computation.

One of the more interesting examples I found is this one:

Here the list is a completely lazy list of three elements. The results are expected: list?, seq?, identity do not do anything with their argument, which is not evaluated (by the way list? is false, seq? is true). first has to evaluate the first element (which is expected), empty evaluates the first element to see if there is such an element. seq should return nil for empty sequences, and as a consequence has to evaluate the first element.

rest also evaluates the first element, but not the successive. I'm not sure if it would be possible to return the cdr of the list without evaluating the car; probably not with clojure lazyness model. next is worse: it evaluates the first element (like rest), but then calls seq on the result, and that leads to the execution of another element.

lazy-seq and list

This example is slightly different, but is trivial, indeed. There is just one lazy seq: the contained list has to be evaluated altogether for every function that needs at least one element.

lazy-seq and concat

The following example, started to surprise me.

As far as I could tell, concat should return a lazy sequence over the lists. It is probably my misunderstanding, but I thought that identity, seq, first, list? and empty? should evaluate one or no elements. I decided to write more benchmark functions.

The results are here:

However, when running the first example the perceived time was akin to the second: simply the evaluation was not timed because it was done (at least) when binding in the let form. Then I got the illumination. Laziness is a bitch: the point here is that it is list that is not lazy. By the way, why should it be. So it does not matter what I do: the time is already spent when I get into concat. I understand that this is trivial: however, I find strange having tp reason about "when things happen" the point of laziness being "not reasoning about when things happen".

Anyway, using lazy-seq, things work as expected:

But what about a "warn on eager", which alerts when something that should be lazy is instead eager?

Technorati Tags:, ,