Skip to content

Latest commit

 

History

History
755 lines (530 loc) · 13.9 KB

functional_python.md

File metadata and controls

755 lines (530 loc) · 13.9 KB

Functional Programming in Python

Overview

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

Resources

Built-in Features and Behavior

  • @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 Libraries for Functional Concepts

  • 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

Well-documented!

  • 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

Wrappers (monoids, monads, applicative functors, etc.)

Option / Maybe

Haskell

data Maybe a = Just a | Nothing

let a = Just 1
let b = Nothing

fmap (+1) (a)  -- Just 2
fmap (+1) (b) -- Nothing  

Scala

// 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

Rust

// 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

F#

// 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

Python w/ Expression

# 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

Python w/ Returns

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

Python w/ Pyrusted Maybe

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 / Result / Try

Haskell

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"

Scala

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"))

Rust

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")

F#

// 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"

Python w/ Expression

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")

Python w/ Returns

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")

Python w/ Pyrusted Result

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")

Immutable List

Haskell

Scala

Rust

F#

Python w/ Expression

Python w/ Returns

Immutable Set

Haskell

Scala

Rust

F#

Python w/ Expression

Python w/ Returns

Immutable Map

Haskell

Scala

Rust

F#

Python w/ Expression

Python w/ Returns

IO

Haskell

Scala

Rust

F#

Python w/ Expression

Python w/ Returns

State

Haskell

Scala

Rust

F#

Python w/ Expression

Python w/ Returns

Reader

Haskell

Scala

Rust

F#

Python w/ Expression

Python w/ Returns

Writer

Haskell

Scala

Rust

F#

Python w/ Expression

Python w/ Returns

Pattern Matching

Scala

// Option[T] is Some[T] or None
val s = Some(1)

s match {
    case Some(x) => println(s"$x")
    case None    => println("no value")
}

Rust

let a = Some(1);
match a {
    Some(x) => print("answer: ", x),
    Nothing => print("no value"),
}

F#

match validInt with
| Some x -> printfn "the valid value is %A" x
| None -> printfn "the value is None"

Python w/ Expression

# 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))

Python w/ Returns

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')

Functions

Higher-order Functions

Function Composition

Currying

Python Standard Library

Immutability

frozenlist use tuple instead of list

Basic HOFs

map(f, xs) and filter(f, xs) are built-ins. functools.reduce(f, xs[, init]) can be used either as reduce or foldl.

Listcomps and Genexps

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

functools docs

  • reduce(function, iterable[, initializer])
  • partial(func, /, *args, **keywords)

itertools

itertools

Other

  • from operator import mul
  • itemgetter

Other Libraries

Monads

  • PyMonad - Not well-documented, no commits for a year.

function-oriented libraries (no wrapper types)

  • 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."

Python extension languages

  • Coconut "is a variant of Python that adds on top of Python syntax new features for simple, elegant, Pythonic functional programming."