- Functional Programming in Python
- Overview
- Resources
- Built-in Features and Behavior
- Python Libraries for Functional Concepts
- Wrappers (monoids, monads, applicative functors, etc.)
- Pattern Matching
- Functions
- Python Standard Library
- Other Libraries
What do we mean by functional programming?
- First-class and higher-order functions
- pure functions / methods
- immutable (persistent) data structures
- static typing
- data classes + functions instead of classes + methods
- @dataclass(eq=True, order=True, frozen=True) (instead of namedtuple)
- Final[float] (3.8+)
- structural pattern matching (3.10+)
- lambdas
- itertools
- functools
- comprehensions/generators
- persistent data structures - frozenset, frozenmap (3.9+)
- Python 3.9+
- Containers - Option, Result, Try
- Pattern matching constructs for Python 3.9
- Pipelining, composition, currying
- Immutable data structures - Seq, AsyncSeq, FrozenList, Map, AsyncObservable
- Effects - option, result
- Mailbox Processor, Cancellation Token, Disposable
- Python 3.7+
- Containers - Maybe, Result, IO, Future, Context
- Pipelining, Converters (Maybe <=> Result, flatten), pointfree, composition, currying
- do notation
Copy of Rust classes
- Python 3.7+
- Immutable (persistent) data structures - PVector (list), PMap (dict), PSet (set), PRecord (PMap with fixed fields, optional type and invariant checking), PClass (class), PBag (collections.Counter), PList (linked list), PDeque (collections.deque)
- CheckedPVector, CheckedPMap and CheckedPSet
- Transformations, like instar for Clojure
- Evolvers
- freeze/thaw for standard Python interaction
data Maybe a = Just a | Nothing
let a = Just 1
let b = Nothing
fmap (+1) (a) -- Just 2
fmap (+1) (b) -- Nothing
// Option[T] is Some[T] or None
val a = Some(1) // Some(1)
val b = Option(1) // Some(1)
val c = None
val d = Option(null) // None
val e = Some(null) // Some(null)
a.map(_ + 1) // Some(2)
c.map(_ + 1) // None
// enum Option<T> {
// Some(T)
// Nothing
// }
let a = Some(1);
let b = Nothing;
a.map(|n| n + 1) // Some(2)
b.map(|n| n + 1) // None
// type Option<'a> =
// | Some of 'a
// | None
let a = Some 1
let b = None
a |> Option.map (fun v -> v + 1) // Some 2
b |> Option.map (fun v -> v + 1) // None
# Option[T] is Some[T] or Nothing
from expression import Option, Some, Nothing
a: Option[int] = Some(1)
b: Option[int] = Nothing
a.map(lambda n: n + 1) # Some(2)
b.map(lambda n: n + 1) # Nothing
from returns.maybe import Maybe, Some, Nothing, maybe
a1: Maybe[int] = Maybe.from_optional(1) # Some(1)
b1: Maybe[int] = Maybe.from_optional(None) # Nothing
a2: Maybe[int] = Maybe.from_value(1) # Some(1)
b2: Maybe[int] = Maybe.from_value(None) # Some(None)
a1.map(lambda x: x + 1) # Some(2)
b1.map(lambda x: x + 1) # Nothing
a2.map(lambda x: x + 1) # Some(2)
b2.map(lambda x: x + 1) # throws TypeError, None + 1 is invalid
b2.map(lambda x: x + 1 if x else None) # Some(None)
# map, but convert None to Nothing and everything else to Some
a1.bind_optional(lambda x: x + 1 if x else None) # Some(2)
b1.bind_optional(lambda x: x + 1 if x else None) # Nothing
a2.bind_optional(lambda x: x + 1 if x else None) # Some(2)
b2.bind_optional(lambda x: x + 1 if x else None) # Nothing
from rustedpy-maybe import Nothing, Some, Maybe
a1: Maybe[int] = Some(1) # Some(1)
b1: Maybe[int] = Nothing() # Nothing
a1.map(lambda x: x + 1) # Some(2)
b1.map(lambda x: x + 1) # Nothing
a1.unwrap_or_else(2) # 1
b1.unwrap_or_else(2) # 2
Either left or right type, right-biased
data Either a b = Left a | Right b
let a = Right 1
let b = Left "error"
fmap (+1) (a) -- Right 2
fmap (+1) (b) -- Left "error"
Either, right-biased
// Either[A, B] is Left[A] or Right[B], right-biased
val a: Either[String, Integer] = Right(1)
val b: Either[String, Integer] = Left("error")
a.map(_ + 1) // Right(2)
b.map(_ + 1) // Left("error")
Try:
// Try[A] is effectively Either[A, Throwable], Success(v) or Failure(ex), success-biased
import scala.util.{Try, Success, Failure}
var a = Success("a")
var b = Failure(new Exception("bad"))
var ex = Try { throw new Exception("bad") } // Failure(Exception("bad"))
More like Scala's Try than Either.
//enum Result<T, E> {
// Ok(T),
// Err(E),
//}
let a: Result<&str, i32> = Ok(1);
let b: Result<&str, i32> = Err("error");
a.map(|n| n + 1) // Some(2)
b.map(|n| n + 1) // Err("error")
// type Result<'T,'TError> =
// | Ok of ResultValue:'T
// | Error of ErrorValue:'TError
let a = Ok 1
let b = Error "error"
a |> Result.map (fun v -> v + 1) // Ok 2
b |> Result.map (fun v -> v + 1) // Error "error"
from expression import Error, Ok, Result
a: Result[int, str] = Ok(1)
b: Result[int, str] = Error("error")
a.map(lambda n: n + 1) # Ok(2)
b.map(lambda n: n + 1) # Error("error")
from returns.result import Result, Success, Failure
a: Result[int, str] = Success(1)
b: Result[int, str] = Failure("error")
a.map(lambda n: n + 1) # Success(2)
b.map(lambda n: n + 1) # Failure("error")
from result import Ok, Err, Result, is_ok, is_err
a: Result[int, str] = Ok(1)
b: Result[int, str] = Err("error")
a.map(lambda n: n + 1) # Ok(2)
b.map(lambda n: n + 1) # Err("error")
// Option[T] is Some[T] or None
val s = Some(1)
s match {
case Some(x) => println(s"$x")
case None => println("no value")
}
let a = Some(1);
match a {
Some(x) => print("answer: ", x),
Nothing => print("no value"),
}
match validInt with
| Some x -> printfn "the valid value is %A" x
| None -> printfn "the value is None"
# Option[T] is Some[T] or Nothing
from expression import Option, Some, Nothing, match
a: Option[int] = Some(1)
b: Option[int] = Nothing
with match(a) as case:
for value in case(Some[int]):
print("answer: ", x)
if case._: # Some not int or Nothing
print("no value")
for value in a.match(Some):
print(f"value: {value}")
print(a.map(lambda n: n + 1))
from dataclasses import dataclass
from returns.maybe import Maybe, Some, Nothing, maybe
@dataclass
class Thing(object):
name: str
match Some(Thing(name="bike")):
case Some(Thing(name='bike')):
print('bike matched')
case Some(t):
print(f"another thing matched: {t}")
case Nothing: # or case _:
print('Nothing matched')
frozenlist use tuple instead of list
map(f, xs)
and filter(f, xs)
are built-ins. functools.reduce(f, xs[, init])
can be used either as reduce or foldl.
These take the place of most uses of map and filter.
list:
[x*2 for x in xs if x % 2 == 0]
map:
{x:x*2 for x in xs if x % 2 == 0}
set:
{x*2 for x in xs if x % 2 == 0}
generator:
(x*2 for x in xs if x % 2 == 0)
functools docs
- reduce(function, iterable[, initializer])
- partial(func, /, *args, **keywords)
- from operator import mul
- itemgetter
- PyMonad - Not well-documented, no commits for a year.
- Toolz and CyToolz "A set of utility functions for iterators, functions, and dictionaries."
- Funcy "A collection of fancy functional tools focused on practicality."
- More Itertools " In more-itertools we collect additional building blocks, recipes, and routines for working with Python iterables."
- Coconut "is a variant of Python that adds on top of Python syntax new features for simple, elegant, Pythonic functional programming."