[info]en_dmitriid


Tigers, and lions, and bears, oh my!


Keep'em languages coming!
happy
[info]dmitriid
Past Friday I got myself acquainted to Python.

Different people view Python as "nothing to learn" or as "if only I had time to learn" or even as "very difficult to learn".

I had to learn it quick for a very simple reason. I have lots of photos taken at different resolutions. I need to resize them to different sizes (much like Flickr does), create several thumbnails of each image, upload them to a host (Amazon S3) and load information on uploaded images to a database.

The process must be automated, of course.

It all started when I convinced myself that Python Imaging Library is the best library to manipulate images. Indeed:

  1. import Image
  2.  
  3. im = Image.open('path/to/image')
  4. im.thumbnail((800, 800), Image.ANTIALIAS)
  5. im.save('path/to/image')


The line with im.thumbnail((800, 800), Image.ANTIALIAS) is absolute genius. If your image is, say, 1024х768px, then what you get in the end is not a skewed 800х800px, but a proportional 800x600px.

Combined with MySQLDb and Boto this yields a very easy solution to my task. However, that's not the point.

As it turns out, getting to know many languages (even superficially) may give you a huge head start in learning a new programming language. I've had an overview or, sometimes, more than an overview of such languages as Lisp, Haskell, Erlang... How does that help me with Python? Well, I got an imeddiate understanding of the following:

Note: I can bear no responsibility for Haskell codes in the examples :)


Tuples and destructuring assignment

A tuple is a fixed-length list. This list may contain elements that are very different in nature:
  1. # Python tuple
  2. (1, 2, 'hello')
  1. -- Haskell tuple
  2. (1, 2, "hello")
  1. %% Erlang tuple
  2. {1, 2, 'hello'}


Looks similar, doesn't it? :)

Let now get data out of our tuples:
  1. # Python
  2. # А will contain 1
  3. # В will contain 2
  4. # С will contain 'hello'
  5. A, B, C = (1, 2, 'hello')
  1. -- Haskell
  2. -- a will contain 1
  3. -- b will contain 2
  4. -- c will contain 'hello'
  5. (a, b, c) = (1, 2, "hello")
  1. %% Erlang
  2. %% А will contain 1
  3. %% В will contain 2
  4. %% С will contain 'hello'
  5. {A, B, C} = {1, 2, 'hello'}


Certainly, the principles of theses examples are very different. Haskell and Erlang use pattern matching that Python doesn't have. However, it looks very similar and is very easy to understand once you know the other examples. PHP, by the way, has a similar thing:
  1. // А will contain 1
  2. // В will contain 2
  3. // С will contain 'hello'
  4. list($A, $B, $C) = array(1, 2, 'hello')


This, of course, is very different and the inner workings are absolutely different :) One thing about this is quite peculiar, though. Once you get used to these sorts of destructuring assignments, you start using PHP's list and Python's tuples much more often. Because it's so convenient.

Named parameters

I once talked about named parameters. As it turns out, Pythons has them.
  1. # define a function
  2. def myfunc(param='', another_param=1)
  3.     print param, another_param
  4.  
  5.  
  6. # call the function
  7.  
  8. # will print hello 5
  9. myfunc(another_param=5, param='hello')
  10.  
  11. # will print hello 1
  12. myfunc(param='hello')
  13.  
  14. # will print 1
  15. myfunc()


Anonymous functions (lambdas)

  1. # Python
  2. # define a list
  3. li = [1, 2, 3, 5, 9, 10, 256, -3]
  4.  
  5. # get only evens out of the list
  6. li_even = filter(lambda item: item % 2 == 0, li)


Very similar to code in other languages:

  1. -- Haskell
  2. -- define a list
  3. li = [1, 2, 3, 5, 9, 10, 256, -3]
  4.  
  5. -- get only evens out of the list
  6. li_even = filter (\item -> (mod item 2) == 0) li
  1. %% Erlang
  2. %% define a list
  3. Li = [1, 2, 3, 5, 9, 10, 256, -3]
  4.  
  5. %% get only evens out of the list
  6. Li_even = lists:filter(fun(Item) -> Item rem 2 == 0 end, Li)


List comprehensions

Remember how we used to define lists in mathematics? For instance:
  1. S = [1, 2, 3, 4]
  2. M = {x | x in S, x even}


M is all x's where x belongs to S and x is even. Nice languages have ways to define lists in a very mathematical way. This is called list comprehensions. One downside to Python's comprehensions is the syntax. Here are previous examples rewritten to use list comprehensions instead of lambdas:

  1. # Python
  2. # define a list
  3. li = [1, 2, 3, 5, 9, 10, 256, -3]
  4.  
  5. # get only evens out of the list
  6. li_even = [x for x in li if x % 2 == 0]
  1. -- Haskell
  2. -- define a list
  3. li = [1, 2, 3, 5, 9, 10, 256, -3]
  4.  
  5. -- get only evens out of the list
  6. li_even = [x | x <- li, (mod x 2) == 0]
  1. %% Erlang
  2. %% define a list
  3. Li = [1, 2, 3, 5, 9, 10, 256, -3]
  4.  
  5. %% get only evens out of the list
  6. Li_even = [X || X <- Li, X rem 2 == 0]




Anyhow, thanks to other languages the new language (a more or less mainstream language, not K :)) ) is not just easy, it's extreamly easy and can be learned in, say, 15 minutes.



Update: Corrections to Haskell code. Thanks, [info]deni_ok

Named Parameters
happy
[info]dmitriid
Ever since I saw Lisp I've sorely missed named function parameters. What the heck is that? Ok, here goes...

Lisp examples are quoted directly from Practical Common Lisp

Let's refresh our knowledge of WinAPI - the dearly beloved API of all Windows developers. For instance, ShellExecute:
HINSTANCE ShellExecute(      
    HWND hwnd,
    LPCTSTR lpOperation,
    LPCTSTR lpFile,
    LPCTSTR lpParameters,
    LPCTSTR lpDirectory,
    INT nShowCmd
);
On the whole I think I remember which parameters this function requires and why. Not too clearly though. I do remember the first three. The fourth might spring to mind while writing the function. The last too however, are quire elusive and I'm never quite sure which goes where and when. The problem gets worse if you remember that som parameters, hwnd, lpParameters, lpDirectory in our case, are optional and can be replaced with NULLs. So, we would quite often invoke this function like this::
 hResult = ShellExecute(NULL, "open", "path/to/file", NULL, NULL, SW_SHOWDEFAULT);
Scary, huh? Now imagine if we could write this function like this:
 hResult = ShellExecute(operation="open", file="path/to/file");
Beautiful. Now, this is what named parameters are all about.

Let's go back to Lisp. Let's consider the following function:
 (defun foo (&key a b c) (list a b c))

This function accepts three parameters and simply listst them in their order. This is how this function behaves when we invoke it in numerous ways:
(foo)                ==> (NIL NIL NIL)
(foo :a 1)           ==> (1 NIL NIL)
(foo :b 1)           ==> (NIL 1 NIL)
(foo :c 1)           ==> (NIL NIL 1)
(foo :a 1 :c 3)      ==> (1 NIL 3)
(foo :a 1 :b 2 :c 3) ==> (1 2 3)
(foo :a 1 :c 3 :b 2) ==> (1 2 3)

Note the last line. Even though the order or parameters is incorrect, the function still behaves as designed. Now, that's the beauty and power of named parameters. A developer now only needs to know which parameters a function requires, not their not their order.

Unfortunately none of the mainstream languages support named parameters, not natively at least. That is why invoking a function (especially a WinApi function) is a long and tedious process whereas we could simply make do with passing a limited number of named arguments (as my example with ShellExecute shows).

I am currently developing in ColdFusion and it supports named parameters natively (though you cannot honestly call it a mainstream language):
<cffunction name="function" access="public">
    <cfargument name="arg1" type="numeric" required="yes">
    <cfargument name="arg2" type="string" required="no">
    <!--- и так далее --->


</cffunction>
This function is invoked as follows:

<cfset function(arg1=1, arg2='string')>
As in Lisp, the order of arguments is not important as long as they are named.

Some languages (scripting languages, mostly) emulate this behaviour through the use of hashes. PHP anyone?

function a_func($params)
{
    if(isset($params['arg1'])) /* do stuff */
    if(isset($params['arg2'])) /* do stuff */
}
 
/* Calling the function */
$result = a_func(array('arg1' => 123, 'arg2' => 'a string'));
That is, we actually pass an array, but it serves our purpose well enough. Ruby employs exactly the same technique:

# find from ActiveRecord
      def find(*args)
        options = extract_options_from_args!(args)
        validate_find_options(options)
        set_readonly_option!(options)

        case args.first
          when :first then find_initial(options)
          when :all   then find_every(options)
          else             find_from_ids(args, options)
        end
      end

 
# Calling the function:
Person.find(:first, :order => "created_on DESC", :offset => 5)
Person.find(:all, :group => "category")
Person.find(:all, :offset => 10, :limit => 10)
# where Person is an object of type Activerecord
However, none of these can even compare to Lisp's implementation. ColdFusion, for instance, doesn't accept an arbitrary number of parameters (see Lisp's &rest). Hashes both in PHP and Ruby are rather cumbersome because implementation of the function may become convoluted (or you have to resort to using less-than-safe extract or extract_options_from_args! functions). I'm not even talking other languages like С/С++, C# and Java :). Some languages though, like Haskell, Erlang and Nemerle, have pattern matching allows the developer to work arounв named parameters in a very elegant manner, but that is a story for another day.

Home