diff --git a/README.md b/README.md index 43e94b2..1c2d717 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,41 @@ # ZSH higher order functions +You can look at [yogsototh's blogpost] for the idea of higher order functions +in zsh. -You can look at [this article](http://yannesposito.com/Scratch/en/blog/Higher-order-function-in-zsh/) to have more informations. +To install, clone this repo to `~/.zsh/functional` and add -To install just clone it to `~/.zsh/functional` - - mkdir ~/.zsh - git clone https://yogsototh@github.com/yogsototh/zsh_functional.git ~/.zsh/functional - -and add - - # Load functions - func_reps=( ~/.zsh/functional/src ) - for rep in $func_reps; do - fpath=($rep $fpath) - autoload -U $rep/*(:t) - done + . ~/.zsh/functional/load to your `.zshrc` + +## Usage and documentation + +Please refer to the tests so far until I've written simple proper docs for each +function. The function `map` and family will print out documentation for you +when calling them with no arguments. + +Each of the method families, `map`, `each`, `filter` and `fold` are having a +"normal version" accompanied with lambda function version and a arithmetic +lambda version. + +### Lambda functions + +Use the versions ending with `l`, like `mapl`, `eachl`, `foldl`. + +### Arithmetic lambda functions + +Similarly, use the functions ending with `a`. + +## Contributing + +Good idea! Just add a test and implement the new functionality and send away +your pull request! :) + +## Creds + +Yann Esposito for the *HoF* idea and big thanks to [Sterling's blogpost] for +discovering and starting implementing the anonymous function features. + +[yogsototh's blogpost]: http://yannesposito.com/Scratch/en/blog/Higher-order-function-in-zsh/ +[Sterling's blogpost]: http://nicholassterling.wordpress.com/2012/03/30/a-zsh-map-function/ diff --git a/load b/load new file mode 100644 index 0000000..db457ea --- /dev/null +++ b/load @@ -0,0 +1,5 @@ +# Load functions +for file in ~/.zsh/functional/src/* ; do + . $file +done + diff --git a/src/each b/src/each new file mode 100644 index 0000000..0150ed5 --- /dev/null +++ b/src/each @@ -0,0 +1,17 @@ +eachl() { + typeset f="$1"; shift + typeset x + typeset result=0 + for x; each_ "$x" "$f" || result=$? + return $result +} + +each_() { + eval "$2" +} + +each() { + typeset f="$1 \"\$1\""; shift + eachl "$f" "$@" +} + diff --git a/src/filter b/src/filter index 2dfc775..9867327 100755 --- a/src/filter +++ b/src/filter @@ -1,29 +1,38 @@ -#!/usr/bin/env zsh # usage: # # $ baz() { print $1 | grep baz } # $ filter baz titi bazaar biz # bazaar -# filter() { - (($#<1)) && { - { - print -- "usage: filter func list" - print - print -- "example:" - print -- ' > baz(){print "$*" | grep baz}' - print -- ' > filter baz titi bazaar biz' - print -- ' bazaar' - } >&2 - return 1 - } - local predicate=$1 - local result - typeset -a result - shift - for elem in $@; do - if eval $predicate $elem >/dev/null; then - result=( $result $elem ) - fi - done - print $result -# } +filter() { + (($#<1)) && { + { + print -- "usage: filter func list" + print + print -- "example:" + print -- ' > baz(){print "$*" | grep baz}' + print -- ' > filter baz titi bazaar biz' + print -- ' bazaar' + } >&2 + return 1 + } + typeset f="$1 \"\$1\""; shift + filterl "$f" "$@" +} + +filterl() { + typeset f="$1"; shift + typeset x + for x; filter_ "$x" "$f" + return 0 +} +filter_() { + eval "$2" && print -- "$1" +} + +### filtera ArithRelation Arg ... # is shorthand for +### filter '(( ArithRelation ))' Arg ... + +filtera() { + typeset f="(( $1 ))"; shift + filterl "$f" "$@" +} diff --git a/src/fold b/src/fold index 2502aa0..0a6a225 100755 --- a/src/fold +++ b/src/fold @@ -1,27 +1,44 @@ -#!/usr/bin/env zsh - if (($#<2)) { - { - print -- 'usage: fold function list' - print - print -- 'example:' - print -- ' > bar() { print $(($1 + $2)) }' - print -- ' > fold bar 0 1 2 3 4 5' - print -- ' 15' - } >&2 - return 1 - } - if (($#<3)) { - print -- $2 - return 0 - } else { - local acc - local right - local func_name=$1 - local init_value=$2 - local first_value=$3 - shift 3 - right=$( fold $func_name $init_value $@ ) - acc=$( eval "$func_name $first_value $right" ) - print -- $acc - return 0 - } +fold () { + if (($#<2)) { + { + print -- 'usage: fold function list' + print + print -- 'example:' + print -- ' > bar() { print $(($1 + $2)) }' + print -- ' > fold bar 0 1 2 3 4 5' + print -- ' 15' + } >&2 + return 1 + } else { + typeset f="\$($1 \$acc \$1)"; shift + foldl "$f" "$@" + } +} + +foldl () { + if (($#<2)) { + { + print -- 'Warning, l is not for left! Its for lambda style expression!' + print -- 'Though this is left fold still :)' + } >&2 + return 1 + } else { + local body=$1 + local acc=$2 + shift 2 + for x; acc=$(fold_ $x $acc $body) + print -- $acc + return 0 + } +} + +folda () { + typeset f="\$[ $1 ]"; shift + foldl "$f" "$@" +} + +fold_ () { + local acc=$2 + local body=$3 + print "${(e)body}" +} diff --git a/src/map b/src/map index 98207fb..be2b720 100755 --- a/src/map +++ b/src/map @@ -1,19 +1,57 @@ -#!/usr/bin/env zsh - map() { - (($#<1)) && { - print -- "usage: map funcname [list]" - print - print -- 'example:' - print -- ' > foo(){print "x: $1"}' - print -- ' > map foo a b c d' - print -- ' x: a' - print -- ' x: b' - print -- ' x: c' - print -- ' x: d' - return 1 - } - local func_name=$1 - shift - for elem in $@; print -- $(eval $func_name $elem) + (($#<1)) && { + print -- "usage: map funcname [list]" + print + print -- 'example:' + print -- ' > foo(){print "x: $1"}' + print -- ' > map foo a b c d' + print -- ' x: a' + print -- ' x: b' + print -- ' x: c' + print -- ' x: d' + return 1 + } + local func_name=$1 + shift + for elem in $@; print -- $(eval $func_name $elem) } + +mapl () { + (($#<1)) && { + print -- "usage: mapl lambda-function [list]" + print + print -- 'example:' + print -- " > mapl 'echo \"x: \$1\"' a b c d" + print -- ' x: a' + print -- ' x: b' + print -- ' x: c' + print -- ' x: d' + return 1 + } + typeset f="$1"; shift + typeset x + typeset result=0 + for x; map_ "$x" "$f" || result=$? + return $result +} + +mapa () { + (($#<1)) && { + print -- "usage: mapa lambda-arithmetic [list]" + print -- " (shorthand for mapl '$[ f ]' [list])" + print + print -- 'example:' + print -- " > mapa '\$1+5' {1..3}" + print -- ' 6' + print -- ' 7' + print -- ' 8' + return 1 + } + typeset f="\$[ $1 ]"; shift + mapl "$f" "$@" +} + +map_ () { + print -- "${(e)2}" +} + diff --git a/test/test b/test/test new file mode 100755 index 0000000..7874002 --- /dev/null +++ b/test/test @@ -0,0 +1,51 @@ +#!/usr/bin/env zsh + +. ~/.zsh/functional/load # load the functions + +# TEST - run a unit test +# +# $1: Test description +# $2: String to eval +# $3: Expected result +TEST() { + desc=$1 + code=$2 + expected="$3 " + result=$(eval $code | tr "\n" " ") + if [[ $result == $expected ]]; then; + echo "SUCCESS: Test '$desc' passed" + else + echo "FAIL: Test '$desc' yielded $result, expected $expected" + fi +} + +if [[ $debug == 1 ]] ; then; + echo "First test the \"test framework\"" + + TEST "THIS SHOULD SUCCESS" "echo 4" "4" + TEST "THIS SHOULD FAIL" "echo 3" "4" + + echo "ok, now lets test what we should test" + echo "\n\n" +fi + +echo "Starting tests of zsh higher order functions" + +plus_one () { echo $(($1+1)) } +divisible_by_two () { (( $1%2 == 0 )) } +addition () { echo $(($1 + $2)) } +ignore_acc () { echo $2 } + +TEST "map can (+1) numbers " "map plus_one {0..5} " "1 2 3 4 5 6" +TEST "mapl echo append " "mapl '\$1 day' good bad " "good day bad day" +TEST "mapa can (+1) " "mapa '\$1 + 5' {1..3} " "6 7 8" +TEST "filter can remove odd numbers " "filter divisible_by_two {0..4} " "0 2 4" +TEST "filterl can grep out words " "filterl 'echo \$1 | grep a --silent' ab bc ac " "ab ac" +TEST "filtera can remove odd numbers " "filtera '\$1%2 == 0' {0..4} " "0 2 4" +TEST "fold can sum numbers " "fold addition 0 {1..5} " "15" +TEST "fold is not commutative " "fold ignore_acc a b c d " "d" +TEST "foldl palin " "foldl '\$1\$acc\$1' MIDDLE = + = = " "==+=MIDDLE=+==" +TEST "foldl palin2 " "foldl '\$1\$2\$1' MIDDLE = + = = " "==+=MIDDLE=+==" +TEST "folda can sum numbers " "folda '\$1+\$2' 0 {1..5} " "15" +TEST "each can only append " "each 'echo young' boy girl " "young boy young girl" +TEST "eachl can prepend " "eachl 'echo \$1 day' good bad " "good day bad day"