PaperCut Blog

Tech & DevCoding

How programming languages have changed over the years

How programming languages have changed over the years

I recently published a blog post introducing the XML-RPC network protocol. To try and help make the content a little more concrete I created various simple examples in different programming languages – namely Python 3, PHP 7, C, Java and Go.

In each case, the programs were small and did the same thing. So it was a great opportunity to see the differences between languages developed over many decades, often with very different design goals.

In January 2018 I was at Linux Conf Australia and on the spur of the moment I got up to give my perspective on what I’d seen (these are called lightning talks in the conference jargon and you only get five minutes to present). The lack of preparation really showed and I didn’t get to my conclusion, so I thought I would give myself a bit more space and provide a slightly more considered opinion, although it’s of course very personnel and subjective.

I’ve also glossed over a lot of details and I’m only discussing a few topics that jumped out at me. Language design and library implementation are complex topics; I encourage you to do further research.

Dynamic vs Static languages

So let’s compare the Python and Go examples. In Python, making a remote procedure call is easy:

result = proxy.userExists(testUser)

Just provide some Python data as parameters, call a method on the proxy object and get back a result in a Python data variable. All the heavy work is done for us.

However in Go we need a lot more scaffolding.

First we have to set up a structure specific to the arguments we need to pass:

var args interface{}

args = &struct { // Magic Data Structure
	Name string // All members are exported
}{
	testUser,
}

We then have to encode the method name and the argument into an XML structure:

buf, _ := xml.EncodeClientRequest("userExists", args)

And then we can make the call:

response, err := http.Post("http://"+host+":"+port+"/users",
							"text/xml",
							bytes.NewBuffer(buf))

Eventually we can then decode the XML return values:

var reply struct{ Present bool } // Magic data strcuture
err = xml.DecodeClientResponse(response.Body, &reply)

Phew!

You can see there is a lot more effort to set up data structures that are specific to each set of parameters and return values used in the RPC calls.

The XML-RPC library uses this type information supplied by the calling client to marshall and unmarshall the XML data.

Note that in Go we also have to pass the method name as a string, instead of pretending it’s a defined method on the ServerProxy class in Python. Python can do this because of some cleverness it exposes with the

__getattr__

And:

__call__

methods. You look in the Python xmlrpc.client source code to see how this is done.

Even in some dynamic languages such as PHP, the marshalling and unmarshalling of arguments into XML is handled separately to the method call. For instance:

$request = xmlrpc_encode_request("userExists", "alec");

And:

$answer = xmlrpc_decode($response);

However the actual HTTP POST request is made with the PHP cURL library, which feels a bit crude:

function do_call($request) {

		$url = "http://localhost:8080/users";
		$header[] = "Content-type: text/xml";
		$header[] = "Content-length: ".strlen($request);

		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($ch, CURLOPT_TIMEOUT, 1);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
		curl_setopt($ch, CURLOPT_POSTFIELDS, $request);

	$data = curl_exec($ch);
	if (curl_errno($ch)) {
			print curl_error($ch);
	} else {
		curl_close($ch);
		return $data;
	}
}

Note that I have limited PHP experience and there could well be a better way to do this. Please feel free to leave a comment below. I would wrap this into my own library of course, but out of the box it’s not as slick as the Python library.

Garbage collection

Most modern languages provide garbage collection and track which data values are in use. However C, designed in the early 1970’s, does not have such a feature and the programmer must do a lot more work to make sure the xmlrpc-c library uses memory correctly.

The code is littered with statements such as:

/* Initialize our error-handling environment. */
xmlrpc_env_init(&env);

xmlrpc_client_setup_global_const(&env);

And:

/* Dispose of our result value. */
xmlrpc_DECREF(resultP);

However to be fair, a lot of this effort is required to support multi threaded C programs. I am glibly assuming that the Python xmlrpc.client works well with the threading library, although I’ve not tried it yet (I can’t find anything in the docs or code to indicate it would not work).

The actual method call in C is fairly simple because of the hard work done by the library authors:

xmlrpc_client_call2f(&env, clientP, url, "userExists", &resultP,
				"(s)", testUser );

Although the user has to provide more information because C does not support type reflection. There is some effort required to manage return values:

xmlrpc_read_bool(&env, resultP, &found);

This becomes quite onerous when unmarshalling data structures. For example:

xmlrpc_value *UUIDp;

xmlrpc_struct_find_value(&env, resultP, "UUID", &UUIDp);

	if (UUIDp) {
		const char *  UUID;
		xmlrpc_read_string(&env, UUIDp, &UUID);
		printf("Called getUserAllDetails() on user %s\nUUID is %s\n", testUser, UUID);

		xmlrpc_DECREF(UUIDp); // Don't need this anymore
	} else
		printf("There is no member named 'UUID'");

/* Dispose of our result value. */
xmlrpc_DECREF(resultP);

Error handling

It’s also worth comparing the way that the languages allow you to handle errors.

In C, handling errors was always a topic of often heated debate and each programmer could choose from a wide range of options. In Python we can (and should) use exceptions, and Go has firm conventions on how to handle errors.

C developers often exercised their personal taste on how to manage errors, which could lead to some disturbing differences across code bases (I used to do a lot of C coding back in the late ’80s).

A final thought

This initially started out as a curious meander through some code. However, as other people have observed before me, solving the same (small) problem in different languages can lead to greater insight into how to improve the use of your current toolset when you need to look at bigger problems.

If you design APIs, attempting this exercise will give you much greater insights into the consequences of your choices on the developers who use your API, and how you can provide a better developer interface.

 

 

Comments