python extension which mimics J syntax
The code implements a domain-specific language for functional programming where:
- Functions are first-class citizens
- Composition is emphasized over explicit variable binding
- Mathematical notation is preserved through operator overloading
- Higher-order functions and adverbs provide powerful abstractions
This creates a Python implementation of a DSL with concise syntax for expressing complex functional transformations, particularly useful for mathematical and data processing operations. The system is inspired by languages like J, K and APL, which emphasize mathematical notation and functional programming concepts. Let me break down its use cases and examples:
Write functions without explicitly naming parameters, similar to APL/J's tacit programming style.
Chain operations and create new functions through operator overloading.
Support for adverbs (functions that transform other functions) and conjunctions.
The system allows writing functions without explicit variable names:
# Instead of: lambda x: x**2
sqr = _**2
# Instead of: lambda x, y: x + y
add = _ + _
# Instead of: lambda x: x + 1
inc = _ + 1
f >> g
means "apply f, then g" (right composition)f << g
means "apply g, then f" (left composition)f @ g
can apply a function g times to f
fork
: Creates a function that applies the same argument twiceflatmap
: Maps a function over a list and flattens resultsins
/insert
: Reduces a list with an operatorrmap
/lmap
: Maps functions over lists in different ways
# Create functions
sqr = fn("x**2")
sqrt = fn(lambda x: x**0.5)
# Function composition
composition = sqr >> sqrt # sqrt(sqr(x))
# Point-free operators
add = _ + _ # x + y
inc = _ + 1 # x + 1
square = _**2 # x**2
# Higher-order operations
double_map = flatmap(lambda x: [x, x]) # Maps each element to [x, x]
# Complex expressions
primes = R >> fork(_unless_ << fork(ε << table(X*Y)@fork@R)@µ)
By using the flatmap adverb loops can be completely avoided:
for i in arr:
do_something(i)`
can now be written as:
arr >> do_something@flatmap
It defines three different function classes (two verbs and one adverb):
- Single argument functions (aka monads)
fn(lambda x: f(x))
- Dual argument functions (aka infix operators aka dyads)
op(lambda x,y: g(x,y))
- Functors (aka functions on functions aka adverbs) for monads and dyads
ad(lambda f: fn(g(f)))
Six overloaded operators which operate on the above classes:
- Data flow operators >> and << which specify the data flow from one verb to another
(f << g)(x) == f(g(x))
and(f >> g)(x) == g(f(x))
- Inline function decorator which modifies a function in place:
(f@g)(x) == (g(f))(x)
- Function power operator for repeated application of a function:
(f^3)(x) == f(f(f(x)))
The data flow operators show their full potential when they are combined with dyadic functions. e.g. Div(x,y)==x/y
and the fork adverb
avg = (_@sum) / (_@len)
where the fork adverb is defined by (F@fork)(x)==F(x,x)
code | meaning | example |
---|---|---|
x >> f |
f(x) |
x >> sin_ |
f << x |
f(x) |
sin_ << x |
f >> g |
g(f(x)) |
sin_ >> cos_ |
f@g |
f(g(x)) |
sin_@cos_ |
f << g |
f(g(x)) |
sin_ << cos__ |
f(g) |
f(g(x)) |
sin_(cos_) |
f >> op << g |
op(f(x),g(y)) |
sin_ >>_+_<< cos_ |
f + g |
f(x)+g(x) |
sin_ + cos_ |
f >> op |
op(f(x),y) |
sin_ >> _+_ |
f(op) |
f(op(x,y)) |
sin_(_+_) |
op >> f |
f(op(x,y)) |
_+_ >> sin_ |
op << f |
op(x,f(y)) |
_+_ << sin_ |
op@ad |
ad(op) |
(_+_)@fork |
f@ad |
ad(f) |
sin_@flatmap |
f@N |
f(f(f...(f(x)))) |
sin_@4 |
N>>op |
op(N,x) |
2>>_+_ |
op<<N |
op(x,N) |
_+_<<4 |
op(N) |
op(x,N) |
(_+_)(3) |
op1+op2 |
op1(x,y)+op2(x,y) |
(_+_)/(_-_) |
_@lambda |
fn(lambda) |
[email protected] |
_+_ |
x+y |
_*_ |
_+N |
x+N |
_*2 |
_[i] |
x[i] |
_[-1] |
_.func |
x.func(y) |
_.split("") |
_(op1, op2, op3) |
op2(op1(x,y),op3(x,y) |
_((_+_),(_/_),(_-_)) |
op1(op2,op3) |
op1(op2(x,y),op3(x,y) |
pow_(_+_,_-_) |
This code defines a functional programming framework in Python that allows for point-free (tacit) programming using custom classes and operator overloading. Here's a breakdown of the main components:
- Represents mathematical functions
- Can be created from strings like
"x**2"
or lambda functions - Supports function composition and arithmetic operations
- Implements overloaded operators for function manipulation:
|
for right composition (f >> g
)^
for left composition (f << g
)@
for function decorators and repeated application>>
and<<
for function composition- Arithmetic operators (
+
,-
,*
,/
,**
) for function arithmetic
- Represents binary operators
- Can be created from strings like
"x+y"
or lambda functions - Supports operator composition and arithmetic operations
- Implements operators for currying and function application
- Represents adverbs (functions that take functions and return functions)
- Examples include
fork
,flatmap
,ins
(insert/reduce),rmap
,lmap
, etc. - Provides higher-order functionality for manipulating functions
- Represents the placeholder
_
for point-free notation - Enables syntax like
(_+_)
for binary operations and(_+2)
for unary operations - Supports currying and operator creation
# Create functions from strings or lambdas
sqr = fn("x**2")
sqrt = fn(lambda x: x**0.5)
# Function composition with >> and <<
result = sqr >> sqrt # sqrt(sqr(x))
# Binary operators
add = op("x+y")
mul = op(lambda x,y: x*y)
# Operators can be curried
add2 = add(2) # x + 2
# `_` represents "any argument"
add = _ + _ # equivalent to op("x+y")
add2 = _ + 2 # equivalent to op("x+2")
f@g
denotes the concatination of two functions f(g(x))
It can be used to generate a new function without writing out an argument. This is called tacit programming. New functions are composed from existing by operators. This is very similar in the programming language Haskell where tacit programming is also a very important tool to generate new functions. In order to accomplish this task in J there exist adverbs and conjunctions. Adverbs are functions with a function as one argument and returning a new function. Conjuctions take 2 functions as arguments and return a new function. They cannot be applied to numers but only on functions and are somehow function-functions or called functors. An example for a adverb is the map
functor which takes as an argument a function of a scalar variable and is transformed into a function which applies this function to an array or list.
- predefined adverbs in iverson.py:
fork, flatmap, deepmap, ins, rmap, lmap, rlmap, table, rev, cum, converge, append, tee, rec
- predefined conjunctions in iverson.py:
f|g, f^g, f>>g, f<<g, f+g, f-g, f*g, f/g, f%g, f**g, f>g, f<g
these adverbs and conjunctions can be used to write tacit programs where no explicit variables are needed.
Furthermore the functions to which these operators are applied to do not have to be only functions of a single variable (aka monads) but can also be functions of 2 variables (aka dyads).
For example the function to sum up an array of numbers can be defined by: sum = (_+_)@ins
which means insert
the function (_+_)
into the array between the numbers and add all the elements.
By using the function one=fn(lambda x:1)
we can then define a length function for arrays by using the flatmap
adverb by length = sum@(one@flatmap)
and furthermore a function mean = sum/length
which computes the sum and the length of an array and then divides the two values by each other giving the mean of the array.
This code directly mimics APL/J concepts:
- Monadic and Dyadic Operators -
fn
andop
classes - Function Composition -
>>
(right compose) and<<
(left compose) - Adverbs -
fork
,flatmap
,ins
, etc. - Tacit Programming - Point-free style using
_
and operators - Array Operations -
table
,rmap
,lmap
, etc.
J Language:
f =: +/ @: *
g =: f @: i.
Python Equivalent:
f = sum_ @ ins(_*_ ) # sum_ is insert operator
g = f @ range_ # apply to range
# Find primes < 100
primes = R >> fork(_unless_ << fork(ε << table(X*Y)@fork@R)@µ)
# Generate Fibonacci numbers
Fib = append(_[-1] + _[-2])
result = [1,1] >> Fib@10 # First 10 Fibonacci numbers
# Estimate pi using random sampling
pi_estimate = 1000000 >> ψ(ψ(χ>>rlµ(_**2>>_+_<<_**2>>_**0.5>>(_<1))<<χ) >> Σ*4 >> _/_)
# Map square to list
squares = sqr @ [1,2,3,4] # [1,4,9,16]
# Apply function 5 times
result = f@5(x) # f(f(f(f(f(x)))))
- Operator Overloading: Uses
>>
,<<
,@
,|
,^
for functional composition - Adverbs: Higher-order functions that modify functions (
fork
,flatmap
,ins
) - Currying: Operators can be partially applied
- Point-free Style: Write functions without explicit variable names
- Tacit Programming: Express computations in terms of function composition
APL/J | Python (this implementation) | Purpose |
---|---|---|
+/ |
sum_ |
Sum reduction |
f g h |
f >> g >> h |
Function composition |
f@ |
f@ |
Adverb application |
_ |
_ |
Placeholder for arguments |
∘ |
>> |
Function composition |
⍥ |
@ |
Power/iteration |
This implementation essentially brings the elegant mathematical and functional programming concepts of APL and J into Python's syntax, enabling concise, expressive code for data transformations and mathematical operations.