When programming functionally in Python

Speaker: Apua

Date: 2015.06.05

Why talk about this?

FP is hot...

  • Facebook uses FP on news feed
  • C++11 and Java8 provides λ calculus
  • Reacts.js - ClojureScript in FP
  • Scala runs on JVM
  • F# runs on .NET framework

Some theories

  • (196x) Domain theory
  • (194x) Category theory
  • (193x) Lambda calculus
  • (192x) Combinatory logic
  • (190x) Type theory
  • ....

Python supports FP...?

  • Python documentation:
    "FP HOWTO"
  • Standard libraries:
    • itertools
    • functools
    • operators
  • Language supports:
    • First class function
    • Generator

...not enough?

Before discussion...

What we do care is:

  • Easy to develop 
  • Easy to maintain

=> Modularization?

The most important thing

"Why Functional Programming Matters"

Functional programming languages provide two new kinds of glue — higher-order functions and lazy evaluationUsing these glues one can modularize programs in new and useful ways, ...

Supports well~

Something wrong with Python...

Wrong value return?

s = '1 2 3'

t = ' '.join((s, '4'))
# t == '1 2 3 4'

L = ' '.join((s, '4')).split()
# L == ['1','2','3','4']

S = ' '.join((s, '4')).split().append('5')
# S is None

Generator failure?

>>> R3 = range(3)
>>> for i in R3:
...   print(i)
... 
0
1
2
>>> for i in R3:
...   print(i)
... 
0
1
2
>>> T = (0,1,2)
>>> G3 = (i for i in T)
>>> for i in G3:
...     print(i)
... 
0
1
2
>>> for i in G3:
...     print(i)
...
def fibs():
    yield 0
    yield 1
    yield from zipWith(add, fibs(), tail(fibs()))

def tail(G):
    next(G)
    yield from G

def zipWith(f, S, G):
    for s,g in zip(S, G):
        yield f(s,g)

add = int.__add__
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
import asyncio
import datetime

@asyncio.coroutine
def display_date(loop):
    end_time = loop.time() + 5.0
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        yield from asyncio.sleep(1)

loop = asyncio.get_event_loop()
loop.run_until_complete(display_date(loop))
loop.close()

Broken closure?

>>> fs = [(λ x: x+y) for y in range(10)]
>>> fs[0](1)
10
> let fs = [(\x -> x+y) | y<-[0..9]]
> (fs !! 0) 1
1
>>> fs = ((λ x: x+y) for y in range(10))
>>> next(fs)(1)
1
>>> fs = map((λ y: λ x: x+y), range(10))
>>> next(fs)(1)
1
>>> fs = [(λ x: x+y) for y in range(10)]
>>> fs[0](1)
10
>>> fs = []
>>> for y in range(10):
...     fs.append((λ x:x+y))
... 
>>> fs[0](1)
10

Nothing wrong with Python~

Python doesn't provide...

Abstract Data Type (ADT)

  1. Algebraic Data Type
  2. Recursive Data Type
  3. Parametric or generic
> data List a = Nil | Cons a (List a)
> :t Nil
Nil :: List a
> :t Cons 1 Nil
Cons 1 Nil :: Num a => List a
class List:
    def __init__(self):
        self._list= []
    def cons(self, item):
        assert isinstance(item, A)
        return [item] + self._list
class L(type):
    def __new__(tcls, cls):
        def init(self, *a):
            self._data = [a[0]]+a[1]._data if a else []

        name = tcls.__name__+' '+cls.__name__
        namespace = {'_cls': cls, '__init__': init}
        wrapped_cls = type(name, (), namespace)
        return wrapped_cls

A = int
LA = L(A)

def nil():
    return LA()

def cons(a, b):
    assert isinstance(a, LA._cls)
    assert isinstance(b, LA)
    return LA(a, b)
data Tree a = Tip
            | Node { value :: a,
                     left  :: Tree a,
                     right :: Tree a }
class TreeA(A, metaclass=Tree):
    def get_value(self):
        return self._node['value']
    def get_left(self):
        return self._node['left']
    def get_right(self):
        return self._node['right']
data Tree a = Tip | Node a (Tree a) (Tree a)

value :: Tree a -> a
left  :: Tree a -> Tree a
right :: Tree a -> Tree a
value (Node value left right) = value
left  (Node value left right) = left
right (Node value left right) = right

Pattern matching

data Tree a = Tip | Node a (Tree a) (Tree a)

value :: Tree a -> a
left  :: Tree a -> Tree a
right :: Tree a -> Tree a
value (Node value _ _) = value
left  (Node _ left _)  = left
right (Node _ _ right) = right
def very_long_name(*lots_of_vars):
    ...
    return state, the_important_value

#value = very_long_name(*lots_of_vars)[1]
_, value = very_long_name(*lots_of_vars)

Type class

  • Not "Class" of OO
  • Another form of data abstraction
  • More abstract than ADT
class Tree t where
    nil   :: t a
    node  :: a -> t a -> t a -> t a
    value :: t a -> Maybe a
    left  :: t a -> Maybe (t a)
    right :: t a -> Maybe (t a)
data Tree a = Tip
            | Node { value :: a,
                     left  :: Tree a,
                     right :: Tree a }
class Tree t where
    nil   :: t a
    node  :: a -> t a -> t a -> t a
    value :: t a -> Maybe a
    left  :: t a -> Maybe (t a)
    right :: t a -> Maybe (t a)
data T a = I | N a (T a) (T a)
instance Tree T where
    nil             = I
    node  v l r     = N v l r
    value I         = Nothing
    value (N v l r) = Just v
    left  I         = Nothing
    left  (N v l r) = Just l
    right I         = Nothing
    right (N v l r) = Just r
class Tree t where
    nil   :: t a
    node  :: a -> t a -> t a -> t a
    value :: t a -> Maybe a
    left  :: t a -> Maybe (t a)
    right :: t a -> Maybe (t a)
class Tree(metaclass=ABCMeta):
    @staticmethod
    @abstractmethod
    def tree_nil():
        ...
    @staticmethod
    @abstractmethod
    def tree_node(v, l, r):
        ...
    @abstract_method
    def get_value(self):
        ...
    @abstract_method
    def get_left(self):
        ...
    @abstract_method
    def get_right(self):
        ...

Monad

class Monad m where
    return :: a -> m a
    (>>=)  :: m a -> (a -> m b) -> m b
data Maybe a = Nothing | Just a   
instance Monad Maybe where
    return a = Just a
    (>>=) (Just a)  f = f a
    (>>=) (Nothing) f = Nothing
data T a = I | N a I I
instance Monad T where
    return a          = N a I I
    (>>=) (N a _ _) f = f a
    (>>=) I         f = I
divide y x = if y==0
             then Nothing
             else Just (x/y)

add y x = Just (x+y)
Just 1 >>= (add 3) >>= (divide 3)
-- Just 1.3333

Just 1 >>= (divide 0) >>= (add 1)
-- Nothing
fd = open('xxx','w')
fd.write('...')
fd.close()
do {
    fd <- openFile "xxx" WriteMode;
    hPutStr fd "...";
    hClose fd;
}
-- :: IO a
fd = open('xxx', 'w')
try:
    fd.write('...')
finally:
    fd.close()
with open('xxx', 'w') as fd:
    fd.write('...')
withFile "xxx" WriteMode (\fd -> do
    hPutStr fd "...")

Monadic

Types for function?

Annotation for type checking

Not really...

Duck typing

Don't care type system?

Wrong type cause design bug?

Generic function

def fcn(a, b=None, *args, **kwargs): ...
		
def fcn(a, *, b=None, **kwargs): ...
def grab_image(path=None, stream=None, url=None):
    r"""
    >>> img = grab_image(path='my.png')

    >>> import sys
    >>> img = grab_image(stream=sys.stdin)

    >>> img = grab_image(path='http://localhost')
    """
    ...
    return img
grab_image(path='my.png',
           stream=sys.stdin,
           url='http://localhost')
@singledispatch
def grab_image(src):
    raise NotImplementedError

@grab_image.register(str)
def _(path):
    ...
    return img

@grab_image.register(Stream)
def _(stream):
    ...
    return img
class Stream(metaclass=ABCMeta):
    @classmethod
    def __subclasshook__(cls, C):
        return hasattr(C, 'read')

Yes, Python lacks something,

but......

Not on the same page

imperative ⇔ declarative

interpreted ⇔ compiled

dynamic typed ⇔ static typed

Python does support FP

...does support...

References

  • http://en.wikipedia.org/wiki/Programming_paradigm
  • https://wiki.haskell.org/Abstract_data_type

  • http://en.wikipedia.org/wiki/Abstract_type

  • http://en.wikipedia.org/wiki/Transactional_memory#Motivation

  • https://docs.python.org/3/library/itertools.html#itertools.tee

  • https://docs.python.org/3/tutorial/classes.html#random-remarks

  • http://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html

  • http://en.wikipedia.org/wiki/Monad_%28functional_programming%29#Formal_definition

  • https://docs.python.org/3/library/asyncio-task.html

  • http://pyos.github.io/dg/

  • http://doc.ponyorm.com/

  • https://github.com/lihaoyi/macropy

  • https://wiki.python.org/moin/PythonVsHaskell

References

  • PEP 484 -- Type Hints
  • PEP 343 -- The "with" Statement
  • PEP 342 -- Coroutines via Enhanced Generators
  • PEP 380 -- Syntax for Delegating to a Subgenerator
  • PEP 3102 -- Keyword-Only Arguments
  • Functional Programming HOWTO
  • Why functional programming matters

Python and FP

By Apua A.Aa

Python and FP

From my note about how FP in Python. The slide is used to the regular talk at PyConAPAC 2015.

  • 2,083