• Stars
    star
    119
  • Rank 297,930 (Top 6 %)
  • Language
    Python
  • License
    MIT License
  • Created over 8 years ago
  • Updated over 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Functional stuff for Python

f is a set of functional tools for Python

Functions

A bunch of useful functions to work with data structures.

Protected call (comes from Lua):

import f

f.pcall(lambda a, b: a / b, 4, 2)
>>> (None, 2)

f.pcall(lambda a, b: a / b, 4, 0)
>>> (ZeroDivisionError('integer division or modulo by zero'), None)

Or use it like a decorator:

@f.pcall_wraps
def func(a, b):
    return a / b

func(4, 2)
>>> (None, 2)

func(4, 0)
>>> (ZeroDivisionError('integer division or modulo by zero'), None)

Attribute and item chain functions handling exceptions:

# let's say, you have a schema with the following foreign keys:
# Order --> Office --> Department --> Chief

order = Order.objects.get(id=42)

# OK
f.achain(model, 'office', 'department', 'chief', 'name')
>>> John

# Now imagine the `department` field is null-able and has NULL in the DB:
f.achain(model, 'office', 'department', 'chief', 'name')
>>> None
data = json.loads('{"result": [{"kids": [{"age": 7, "name": "Leo"}, {"age": 1, "name": "Ann"}], "name": "Ivan"}, {"kids": null, "name": "Juan"}]}')

# OK
f.ichain(data, 'result', 0, 'kids', 0, 'age')
>>> 7

# the chain is broken
f.ichain(data, 'result', 42, 'kids', 0, 'age')
>> None

Threading functions from Clojure

The first threading macro puts the value into an each form as a first argument to a function:

f.arr1(
    -42,                        # initial value
    (lambda a, b: a + b, 2),    # form
    abs,                        # form
    str,                        # form
    (str.replace, "40", "__")   # form
)
>>> "__"

The second threading macro is just the same, but puts a value at the end:

f.arr2(
    -2,
    abs,
    (lambda a, b: a + b, 2),
    str,
    ("000".replace, "0")
)
>>> "444"

Function composition:

comp = f.comp(abs, (lambda x: x * 2), str)
comp(-42)
>>> "84"

Every predicate

Composes a super predicate from the passed ones:

pred1 = f.p_gt(0)
pred2 = f.p_even
pred3 = f.p_not_eq(666)

every = f.every_pred(pred1, pred2, pred3)

result = filter(every, (-1, 1, -2, 2, 3, 4, 666, -3, 1, 2))
tuple(result)
>>> (2, 4, 2)

Transducer: a quick-and-dirty port from Clojure

f.transduce(
    (lambda x: x + 1),
    (lambda res, item: res + str(item)),
    (1, 2, 3),
    ""
)
>>> "234"

Nth element getters

f.first((1, 2, 3))
>>> 1

f.second((1, 2, 3))
>>> 2

f.third((1, 2, 3))
>>> 3

f.nth(0, [1, 2, 3])
>>> 1

f.nth(9, [1, 2, 3])
>>> None

Predicates

A set of unary and binary predicates.

Unary example:

f.p_str("test")
>>> True

f.p_str(0)
>>> False

f.p_str(u"test")
>>> True

# checks for both int and float types
f.p_num(1), f.p_num(1.0)
>>> True, True

f.p_list([])
>>> True

f.p_truth(1)
>>> True

f.p_truth(None)
>>> False

f.p_none(None)
>>> True

Binary example:

p = f.p_gt(0)

p(1), p(100), p(0), p(-1)
>>> True, True, False, False

p = f.p_gte(0)
p(0), p(1), p(-1)
>>> True, True, False

p = f.p_eq(42)
p(42), p(False)
>>> True, False

ob1 = object()
p = f.p_is(ob1)
p(object())
>>> False
p(ob1)
>>> True

p = f.p_in((1, 2, 3))
p(1), p(3)
>>> True, True
p(4)
>>> False

You may combine predicates with f.comp or f.every_pred:

# checks for positive even number
pred = f.every_pred(f.p_num, f.p_even, f.p_gt(0))
pred(None), pred(-1), pred(5)
>>> False, False, False
pred(6)
>>> True

Collections

Improved collections List, Tuple, Dict and Set with the following features.

Square braces syntax for initiating

f.List[1, 2, 3]     # or just f.L
>>> List[1, 2, 3]

f.T[1, 2, 3]
>>> Tuple(1, 2, 3)

f.Set[1, 2, 3]
>>> Set{1, 2, 3}

f.D[1: 2, 2: 3]
>>> Dict{1: 2, 2: 3}

Additional methods such as .map, .filter, .foreach, .sum, etc:

l1 = f.L[1, 2, 3]
l1.map(str).join("-")
>>> "1-2-3"

result = []

def collect(x, delta=0):
    result.append(x + delta)

l1.foreach(collect, delta=1)
result == [2, 3, 4]
>>> True

See the source code for more methods.

Every method returns a new collection of this type:

l1.filter(f.p_even)
>>> List[2]

l1.group(2)
>>> List[List[1, 2], List[3]]

# filtering a dict:
f.D[1: 1, 2: 2, 0: 2].filter(lambda (k, v): k + v == 2)
>>> Dict{0: 2, 1: 1}

Easy adding two collection of different types

# merging dicts
f.D(a=1, b=2, c=3) + {"d": 4, "e": 5, "f": 5}
>>> Dict{'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4, 'f': 5}

f.S[1, 2, 3] + ["a", 1, "b", 3, "c"]
>>> Set{'a', 1, 2, 3, 'c', 'b'}

# adding list with tuple
f.L[1, 2, 3] + (4, )
List[1, 2, 3, 4]

Quick turning to another collection

f.L["a", 1, "b", 2].group(2).D()
>>> Dict{"a": 1, "b": 2}

f.L[1, 2, 3, 3, 2, 1].S().T()
>>> Tuple[1, 2, 3]

Monads

There are Maybe, Either, Error and IO monads are in the library. Most of them are based on classical Haskell definitions. The main difference is they use predicates instead of type checks.

I had to implement >>= operator as >> (right binary shift). There is also a Python-specific .get() method to fetch an actual value from a monadic instance. Be fair and use it only at the end of the monadic computation!

Maybe

# Define a monadic constructor
MaybeInt = f.maybe(f.p_int)

MaybeInt(2)
>>> Just[2]

MaybeInt("not an int")
>>> Nothing

# Monadic pipeline
MaybeInt(2) >> (lambda x: MaybeInt(x + 2))
>>> Just[4]

# Nothing breaks the pipeline
MaybeInt(2) >> (lambda x: f.Nothing()) >> (lambda x: MaybeInt(x + 2))
>>> Nothing

The better way to engage monads into you project is to use monadic decorators:

@f.maybe_wraps(f.p_num)
def mdiv(a, b):
    if b:
        return a / b
    else:
        return None

mdiv(4, 2)
>>> Just[2]

mdiv(4, 0)
>>> Nothing

Use .bind method as an alias to >>:

MaybeInt(2).bind(lambda x: MaybeInt(x + 1))
>>> Just[3]

You may pass additional arguments to both .bind and >> methods:

MaybeInt(6) >> (mdiv, 2)
>>> Just[3]

MaybeInt(6).bind(mdiv, 2)
>>> Just[3]

Release the final value:

m = MaybeInt(2) >> (lambda x: MaybeInt(x + 2))
m.get()
>>> 3

Either

This monad presents two possible values: Left (negative) and Right (positive).

# create a constructor based on left and right predicates.
EitherStrNum = f.either(f.p_str, f.p_num)

EitherStrNum("error")
>>> Left[error]

EitherStrNum(42)
>>> Right[42]

Right value follows the pipeline, but Left breaks it.

EitherStrNum(1) >> (lambda x: EitherStrNum(x + 1))
>>> Right[2]

EitherStrNum(1) >> (lambda x: EitherStrNum("error")) >> (lambda x: EitherStrNum(x + 1))
>>> Left[error]

When the plain value does not fit both predicates, TypeError occurs:

EitherStrNum(None)
>>> TypeError: Value None doesn't fit...

Use decorator to wrap an existing function with Either logic:

@f.either_wraps(f.p_str, f.p_num)
def ediv(a, b):
    if b == 0:
        return "Div by zero: %s / %s" % (a, b)
    else:
        return a / b


@f.either_wraps(f.p_str, f.p_num)
def esqrt(a):
    if a < 0:
        return "Negative number: %s" % a
    else:
        return math.sqrt(a)


EitherStrNum(16) >> (ediv, 4) >> esqrt
>>> Right[2.0]

EitherStrNum(16) >> (ediv, 0) >> esqrt
>>> Left[Div by zero: 16 / 0]

IO

This monad wraps a function that does I/O operations. All the further calls return monadic instances of the result.

IoPrompt = f.io(lambda prompt: raw_input(prompt))
IoPrompt("Your name: ")  # prompts for you name, I'll type "Ivan" and RET
>>> IO[Ivan]

Or use decorator:

import sys

@f.io_wraps
def input(msg):
    return raw_input(msg)

@f.io_wraps
def write(text, chan):
    chan.write(text)

input("name: ") >> (write, sys.stdout)
>>> name: Ivan
>>> Ivan
>>> IO[None]

Error

Error monad also known as Try in Scala is to prevent rising exceptions. Instead, it provides Success sub-class to wrap positive result and Failture to wrap an occured exception.

Error = f.error(lambda a, b: a / b)

Error(4, 2)
>>> Success[2]

Error(4, 0)
>>> Failture[integer division or modulo by zero]

Getting a value from Failture with .get method will re-rise it. Use .recover method to deal with exception in a safe way.

Error(4, 0).get()
ZeroDivisionError: integer division or modulo by zero

# value variant
Error(4, 0).recover(ZeroDivisionError, 42)
Success[2]

You may pass a tuple of exception classes. A value might be a function that takes a exception instance and returns a proper value:

def handler(e):
    logger.exception(e)
    return 0

Error(4, 0).recover((ZeroDivisionError, TypeError), handler)
>>> Success[0]

Decorator variant:

@f.error_wraps
def tdiv(a, b):
    return a / b


@f.error_wraps
def tsqrt(a):
    return math.sqrt(a)

tdiv(16, 4) >> tsqrt
>>> Success[2.0]

tsqrt(16).bind(tdiv, 2)
>>> Success[2.0]

Generics

Generic is a flexible callable object that may have different strategies depending on a set of predicates (guards).

# Create an instance
gen = f.Generic()

# extend it with handlers
@gen.extend(f.p_int, f.p_str)
def handler1(x, y):
    return str(x) + y

@gen.extend(f.p_int, f.p_int)
def handler2(x, y):
    return x + y

@gen.extend(f.p_str, f.p_str)
def handler3(x, y):
    return x + y + x + y

@gen.extend(f.p_str)
def handler4(x):
    return "-".join(reversed(x))

@gen.extend()
def handler5():
    return 42

@gen.extend(f.p_none)
def handler6(x):
    return gen(1, 2)

# let's try:
gen(None)
>>> 3

gen(1, "2")
>>> "12"

gen(1, 2)
>>> 3

gen("fiz", "baz")
>>> "fizbazfizbaz"

gen("hello")
>>> "o-l-l-e-h"

gen()
>>> 42

# calling without a default handler
gen(1, 2, 3, 4)
>>> TypeError exception goes here...

# now we have one
@gen.default
def default_handler(*args):
    return "default"

gen(1, 2, 3, 4)
>>> "default"

More Repositories

1

pg2

A fast PostgreSQL driver for Clojure
Clojure
101
star
2

remus

Attentive RSS/Atom feed parser for Clojure
Clojure
58
star
3

mockery

Clojure mocking library
Clojure
46
star
4

domic

Clojure
38
star
5

clj-book

Книга «Clojure на производстве»
TeX
35
star
6

dotfiles

My Emacs config and other unix stuff
Emacs Lisp
35
star
7

pact

Chaining values with ease
Clojure
34
star
8

deed

Fast, flexible, 0-deps (de)serialization library for Clojure
Clojure
33
star
9

pg

Deprecated: use igrishaev/pg2
Clojure
29
star
10

user-agent

User-Agent parser for Clojure
Clojure
29
star
11

interview

Технические вопросы с ответами http://grishaev.me/interview/
29
star
12

bogus

A simple GUI debugger for Clojure
Clojure
25
star
13

farseer

A set of modules for handling JSON RPC in Clojure
Clojure
23
star
14

spec-dict

Better map specs
Clojure
18
star
15

virtuoso

A small wrapper on top of Java 21 virtual threads
Clojure
16
star
16

zippo

Additions to the standard clojure.zip package
Clojure
15
star
17

teleward

Captcha bot for Telegram in Clojure + GraalVM
Clojure
13
star
18

mask

Clojure
11
star
19

soothe

Clear error messages for Clojure.spec
Clojure
11
star
20

googl-python

Goo.gl library for Python
10
star
21

dynamodb

Pure Clojure driver for Dynamo DB (with GraalVM/native-image support)
Clojure
10
star
22

queryfeed

An outdated version of Queryfeed
Python
9
star
23

clj-java-book

Clojure Extended: Java interop
Clojure
8
star
24

book-sessions

Clojure
8
star
25

lambda

An AWS Lambda in a single binary file
Clojure
8
star
26

1c-json

JSON-сериализация в 1С
7
star
27

blog

My blog
HTML
7
star
28

dos-and-donts

A short list of rules I try to follow
7
star
29

CV

My CV
Makefile
4
star
30

highloadcup

HighloadCup solution made with Clojure/Datomic
Clojure
4
star
31

clj-bitcoin

Clojure
2
star
32

with-http

Clojure
2
star
33

clj-book2

Книга «Clojure на производстве», второй том
TeX
2
star
34

clj-book-en

TeX
2
star
35

vsu-prog-lang-concept

VSU fiit magisters tasks
JavaScript
1
star
36

blog-backend

Backend for my blog
Clojure
1
star
37

foo-bar

Clojure
1
star
38

mars-task-haskell

Haskell
1
star
39

findcard

Clojure
1
star
40

gr-book

TeX
1
star
41

littlesms-python

API client class for LittleSMS.ru service
1
star
42

barriers-how-to

Все об установке шлагбаумов
Makefile
1
star
43

vsu-django-students

ClojureScript + Reagent Example
Python
1
star