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
Made with Slides.com