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 evaluation. Using 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)
- Algebraic Data Type
- Recursive Data Type
- 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