PyCon X


2nd - 5th May 2019

Thinking functionally: Introduction to FP in Python

Python is an imperative language like Java or C++. An imperative language is based on ‘commands’, in the form of instructions and statements and are executed. These commands will have an effect. Functional languages like Haskell, OCaml revolve around the concept of ‘pure functions’. A pure function is expressed as a mathematical expression only. You can evaluate the expression and there will be no side effects, for example, no I/O operations, no global state changes, no database interactions. The output is only dependent on its inputs. Hence, it is based on ‘equational reasoning’. So if you were to call a pure function with the same inputs a million times, you would get the same result every single time. Since there are no side effects, there is immutable data. The data once initialized remains same throughout the lifetime of the code, which makes ‘parallel programming’ easier. Due to this immutability of data, the value of an expression is the same anywhere it might occur in the program – as long as the required variables are defined. This is termed as ‘Referential Transparency’. It means that the value of an expression is the same anywhere it might occur in the program – as long as the required variables are defined. You never need to track the value of state variables or remember any updates. This enables usage of ‘memoization’ – you basically ‘remember’ the outputs of expensive functions with some common arguments in a sort of lookup table. This reduces the computational complexity at the expense of memory.

Functions are dealt as ‘first-class citizens’, i.e, you can treat them as any other objects – you can assign them to variables, you can pass them as arguments, or even get them returned from other functions. ‘Recursion’ is the most important aspect of functional programming, since there are no loops (which prevents any side effects). ‘Higher order functions’ like map(), reduce() and filter() can take functions as arguments (or return them as results), providing parallelism over sequences. Partial function application, ‘currying’ and ‘lazy evaluation’ prevents loading entire data at once into the memory. All these features provide the advantage of code re-usability, less bugs, easy testing and parallelism.

Although Python is not a functional programming language, but it has features suitable for writing a functional program. The three popular higher order functions are map(), reduce(), filter(). reduce() has now been moved to functools module, a part of the standard library. Next comes the lambda function, which has its roots in the Lambda Calculus. They are small anonymous functions not necessarily bound to a name at runtime. As far as recursion is concerned, any imperative code can be converted into a recursive function. The functions, operator and itertools module groups a number of functions (like iter()) related to iterators and their use/combination. Generator expressions to some extent do lazy evaluation, i.e. return an iterator that computes the values as necessary, not needing to materialize all the values at once. Yield expression is an example of this.

However, there is no optimization for tail recursion in Python. In functional programming languages like Haskell, tail-recursive code is easily optimized into iterative code by the underlying compiler. In Python, it is impossible to separate pure and non-pure functions. Also, there is no pattern matching syntax. The variables are mutable. There is no function overloading mechanism. Moreover, there is only class-based type system. Lazy evaluation is also not fully supported.

Feedback form:

Do you have some questions on this talk?

New comment