Skip to content

Latest commit

 

History

261 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fpmx

[F]unctional [P]rogramming [M]ath e[X]tension for hy-lang.

fpmx is a language extension for Hy and Python that brings ergonomics of Mathematica, APL, Haskell, and similar FP/math-heavy languages directly to the Hy/Python runtime.

fpmx is implemented as a two-tier system:

  1. fpmx.prelude — the stable heart of the library optimized for everyday use. It includes over 250 pure functions, macros and types that define the fpmx experience. By importing whole prelude namespace you gain immediate access to a "batteries-included" functional vocabulary.

    This is the recommended entry point for all projects

  2. fpmx.extras — a collection of modules that contain specialized, domain-specific, or experimental FP/math features.

Using fpmx

fpmx, being language extension rather than just collection of utils, is encouraged to be imported via loader.

For example, to load prelude:

(require fpmx.loader [load_fpmx])
(load_fpmx "prelude")

Which is internally the same as:

(import  fpmx.prelude *) ; load all prelude funcs/types
(require fpmx.prelude *) ; load all prelude macros

Still nothing is forbidding you from importing only required functionality

The reason loader exists is:

  • hy-lang has different syntax for importing functions (via import) and macros (via require) — loader removes necessity to remember which one is which
  • you can combine loading of multiple parts of fpmx in one expression (see below)

Whole list of available modules:

(require fpmx.loader [load_fpmx])
(load_fpmx "prelude"
           "term"
           "lenses"
           "strict_types"
           "maybeM" "resultM" "writerMaybeT"                        ; monad modules
           "strict_maybeM" "strict_resultM" "strict_writerMaybeT")  ; strict monad modules

[1/2] Prelude module

Cheatsheet

To get overall picture of what Prelude offers — see overview of all 250+ fpmx.prelude functions/macros/objects: Cheatsheet (table form)

Key principles

Following the Mathematica tradition, fpmx provides a rich set of boolean predicates (ending in Q for «Query»):

(zeroQ x)        ; checks if x == 0
(negativeQ x)    ; checks if x < 0
(zerolenQ xs)    ; cehcks if len(xs) == 0
(numberQ x)      ; checks if x is int or float
(iterableQ xs)   ; checks if xs is iterable
(noneQ x)        ; checks if x is None
...

Following funcy tradition, most sequence functions offer both lazy and eager list variants (e.g., map vs lmap) allowing you to optimize for memory or speed as needed:

(cycle 'AB')         ; generator: 'A', 'B', 'A', ...
(lcycle 'AB' 3)      ; ['A' 'B' 'A']
(lfilter pred seq)   ; list version of filter
(lreversed sequence) ; list version of reversed
...

To make usage of standard * and + dunders more explicit, fpmx offers duplicated names for them:

(mul  2 3)        ; «*» operator as function
(smul 3 "a")      ; «*» operator as function, but underlines usage on string like (* 3 "a")
(lmul 3 [1 2])    ; «*» operator as function, but underlines usage on lists like (* 3 [1])

(plus 2 3)        ; «+» operator as function
(sconcat "a" "b") ; concatenate strings 
(lconcat [1] [2]  ; eager concatenation of lists

To avoid manual writing of (import typing [List]) in every module, fpmx by default reimports several most commonly used types like List, Dict, Tuple, Union, dataclass and several others.

case, unless, -> and other classic utilities are reimported from hyrule.

Basic operators (like +, /, @, etc.) are also provided as functions (plus, div, matmul, etc.).

Features highlight

Ergonomic wrappers

Functional wrappers for basic IO:

(read_file "1.txt" :encoding "utf-8")   
(write_to_file "1.txt" text :mode "w")
(path_existsQ f) ; checks if file or folder f exists
...

Functional wrappers for regex:

(re_sub r"\d" "-" "smth1smth1smth")   ; returns "smth-smth-smth"
(re_find r"\s*\d\d" "here 20 comes")  ; returns " 20"
...

Various helpers like:

  • lprint to print each elem of iterable on new line
  • cur_time for returning current time

Threading

Crown jewels of fpmx are threading macros => and =>>. See them as a combination of . and ->/->> macros:

(=>> some_data
     function                 ; function application like in `->>` macro
     (function arg1 arg2 ...) ; function application like in `->>` macro
     (.mth arg1 arg2 ...))    ; method calling (`->>` has it broken)
     [0 "key"]                ; index/key access like in `.` macro
     .attr                    ; attribute-access similar to `.` macro

=> and =>> solves the problem of combining getters with threaders:

; consider list of points (Point is dataclass with :x and :y fields):
(setv pts [(Point :x 1 :y 2) (Point :x 3 :y 4)])

; We want to extract .x of first point (=1) and double it (return 2)

; The best we can do in traditional hy syntax is:
(-> pts (get 0) (getattrm "x") double)  
; or:
(double (. pts [0] x))

; now see how fpmx => macro makes it much more prettier:
(=> pts [0] .x (double))  

Sequence processing

fpmx extends Python basic FP-vocabulary:

; enhancing zip/map/reduce family:
(starmap ...)    ; reimport of itertools.starmap
(reductions ...) ; returns sequence of intermediate results of functools.reduce function
...

; cutting and grouping:
(lpartition 2 [0 1 2 3 4 5]) ; returns [[0 1] [2 3] [4 5]]
(lbisect_by trueQ [True True False False False]) ; returns #([True True] [False False False])
...

; filtering:
(fltr1st floatQ [1 2 3.0 4 5]) ; will return 3.0 (or None if float were not present)
(lfilter_split floatQ [1 2.0 3.0 4]) ; will return #([2.0 3.0] [1 4])
...

Math ergonomics

; shortcuts for common operations:
(inc x)  ; x + 1 
(dec x)  ; x - 1 
(half x) ; x / 2
...

; rounding:
(round_to 9.1 1.5)    ; rounds to multiple (of 1.5 in this case)
(floor x)             ; reimport of math.floor
(clip x lower upper)  ; clips x to fit in [lower <= x <= upper] limits
(approx_eq x y)       ; renaming of math.is_close
...

; basic math funcitons:
(sqrt x) ; square root of x
(exp x)  ; exponent function
(ln x)   ; natural logarythm
...

; trigonometry:
(sin x)  ; reimport of math.sin
(atan x) ; artangent of x
pi       ; float pi=3.14...
...

; random:
(rand_int 3 7)       ; random integer in range
(rand_float 1.0 3.5) ; random float in range
...

Functional composition

Set of utility functions that provide true "function-first" experience in Hy/Python:

; nested function application:
(setv nested_fs (compose f1 f2 f3))
(nested_fs x)  ; will essentially run f1(f2(f3(x)))

; partial application:
(lmap (partial plus 3) [1 2 3]) ; returns [4 5 6]

; flipping arguments for 2-args functions:
(lmap (partial div 10) [1 2 3]) ; returns [10.0 5.0 3.33333]
(lmap (pflip   div 10) [1 2 3]) ; returns [0.1 0.2 0.3]

Buffed getters

fpmx offers enhanced getters (and several setters) for making index/attr accessing more ergonomic:

; Named indexed getters, which are common in FP languages:
(setv xs ["a" "b" "c" "d"])
(first xs)  ; returns "a"
(fourth xs) ; returns "d"
(last xs)   ; returns "d"
...

; Sequential getters
(drop 2 [1 2 3 4 5])           ; returns [3 4 5]
(drop -2 [1 2 3 4 5])          ; returns [1 2 3]
(take 2 [1 2 3 4 5])           ; returns [1 2]
(pick [0 2] ["a" "b" "c" "d"]) ; returns ["a" "c"]
...

; Getter that will return None rather than throw error (unlike basic 'get' macro):
(nth 3 xs)  ; will return None if xs[3] does not exist

; Bulk getters:
(lpluck 0 [[0 1] [2 3] [4 5])  ; returns [0 2 4] (e.g. first elem of each sublist)
(lpluck_attr "x" points)       ; gets point.x for every point from points (returns list)
...

Lambdas

Special syntax for lambdas, which removes neccessity to manualy name arguments:

(fm (print it))     ; (fn [it] (print it))          ; «it» is recognized as solo-argument 
(fm (print %1 %2))  ; (fn [%1 %2] (print %1 %2))    ; %1 and %2 are recognized as arguments

; mapm/filterm, their list-variants, and several others use same syntax:
(lmapm (* %1 %2) [1 2 3] [4 5 6])
(filterm (eq it 3) [1 2 3])

Haskell-style function annotation

A special treat for Haskellers — fpmx offers function annotation macro def:::

; basic usage:
(def:: int -> int => float
    [decorator] div_ints [x y] (return ...))
; function div_ints will have annotation: div_ints(x: int, y: int) -> float

; showcase of annotating args and kwarks, plus skipping some annotations with «@»:
(def:: @ -> int -> / -> int -> #* int -> #** dict => int
       f6 [a b / c #* args #** kwargs] (+ a b c))
       ; «a»-arg will have no annotation due to «@»

[2/2] Extra modules

fpmx has following evolving extra modules:

  • fpmx.strict.types:
    • requires pydantic library
    • offers some utils for strict type checking
  • fpmx.monads and fpmx.strict monads:
    • currently contains Maybe and Result monads, together with WriterMaybe transformer
    • they are implemented with opinionated function-first API (contrast it with "method chaining" API, whcih is more common in similar monad libs)
    • strict monads can be pydantic type-checked with pydantic library
  • fpmx lenses
    • offers macros for nicer lens syntax (lens is Haskell-style immutabe getters and setters for working with deeply nested structures)
    • requires lenses library
  • fpmx.term
    • terminal utils: coloring, quick plotting, etc.

Modules that require pydantic may be slower to load, this is one of the reasons for excluding them from Prelude.

Documentation

Prelude

All Prelude functions/types/macros are layed out in 2 different formats:

Detailed guide on fpmx-exclusive macros:

Extra modules

Dependencies

Tested with versions:

hyrule lib is not in the dependencies list, since fpmx internally replicates some of it's macros (like ->, case and others). This is done to increase fpmx startup speed.

Project status

Prelude:

  • functionality is at 90% of reaching stable release
  • some API-breaking changes may still happen, although will probably be very minimal
  • sequence processing functions require polishing in following aspects:
    • clear distinction between lazy and eager functions
    • make type annotations more honest (like List vs Iterable issues)

Extra modules:

  • most of them are considered experimental
  • stable API is not guaranteed

Installation

pip install git+https://github.com/rmnavr/fpmx.git@main

About

Functional programming extension for hy lang

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages