Created
February 26, 2017 20:40
-
-
Save kjkuan/bc9817166b58c255808f86979b6f9a23 to your computer and use it in GitHub Desktop.
For-All Each-Do Success Fail
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# | |
# This is a hack that allows you to express loop and conditional processing | |
# of an array of items in terms of function definitions. | |
# | |
# My initial motivation came from the need to process an array of items, and | |
# then for those successfully processed items, do another different processing | |
# step, and similarly for the failed items; perform further processing/filtering | |
# steps for each failed/successful items. | |
# | |
# The end result does what I wanted, but also made me realize its limitations. | |
# Although it's nice to be able to express the processing steps in a tree-like | |
# fasion via nested function definitions, there's certain performance overhead | |
# incurred by the use of code generation and function calls. | |
# | |
# Moreover, it requires all inputs be stored in an array first in memory, and | |
# more intermediate arrays are created to hold the successful and failed items | |
# as they are processed. And lastly, due to dynamic function rewrites at runtime, | |
# source line numbers in error reports become completely useless. | |
# | |
# However, it's an interesting exercise nevertheless, and I believe more | |
# interesting hacks can come out of the similar ideas or techniques used here. | |
# Therefore, by sharing the source code, I hope you learn or find something | |
# useful from it. Comments and suggestions are welcomed! | |
# | |
# | |
# Example: | |
main () { | |
local inputs results=() | |
inputs=($(seq 10)) || return $? | |
# A Each-Do function definition is required. Its body will be wrapped inside | |
# a loop that iterates through the successful/failed array from the | |
# previous step. | |
Each-Do () { | |
# $IT expands to the current item in the loop | |
echo "Processing number $IT" | |
(( $IT % 2 )) # true if $IT is odd | |
} | |
Success () { # for odd numbers | |
# $_successful is the name of the array holding successfully | |
# processed items from the previous Each-Do() {...} function. | |
# There's also a $_failed. | |
# | |
local -n odd_nums=$_successful | |
local odd_len=${#odd_nums[*]} | |
# You can nest *-Do() definitions inside Success()/Fail(). | |
Each-Do () { | |
echo "$IT is odd" | |
(( IT < odd_len )) | |
} | |
Success-Do () { # alternative for writing just Success() { Each-Do () { ...; }; } | |
echo "$IT is < $odd_len" | |
results+=($(( IT ** 2 ))) | |
} | |
Fail-Do () { # alternative for writing just Fail() { Each-Do () { ...; }; } | |
echo "$IT is >= $odd_len" | |
results+=($IT) | |
} | |
# NOTE: A 'For-All $_successful' at the end here is implicit added. | |
} | |
Fail () { # for even numbers | |
local -n even_nums=$_failed | |
echo "The even numbers are: ${even_nums[*]}" | |
} | |
For-All inputs # kicks off the processing | |
echo === results============= | |
local each | |
for each in ${results[*]}; do | |
echo $each | |
done | |
} | |
# -------------------------------------------------- | |
FOR_ALL_NEXT_ID=0 | |
func2var () { # <func_name> <var_name> | |
local -n f=${2:?required} | |
f=$(declare -f ${1:?required}) || return $? | |
f=${f/$1 /${2}_$((FOR_ALL_NEXT_ID++))_$RANDOM } | |
} | |
For-All () { # <array_name> | |
# $1 should be the name of the array to be processed. | |
# $1 should start with a character matching [a-z] to avoid clashing | |
# with For-All's local variables. | |
local Each_Do; Each_Do=$(declare -f Each-Do) || { | |
echo "$FUNCNAME: Each-Do () { ...; } is required!" >&2 | |
return 1 | |
} | |
local Success Success_Do Fail Fail_Do | |
func2var Success Success | |
func2var Success-Do Success_Do | |
func2var Fail Fail | |
func2var Fail-Do Fail_Do | |
if [[ $Success && $Success_Do ]]; then | |
echo "$FUNCNAME: Success and Success-Do can't be used at the same level!" >&2 | |
return 1 | |
fi | |
if [[ $Fail && $Fail_Do ]]; then | |
echo "$FUNCNAME: Fail and Fail-Do can't be used at the same level!" >&2 | |
return 1 | |
fi | |
local _successful=successful_$((FOR_ALL_NEXT_ID++))_$RANDOM | |
local _failed=failed_$((FOR_ALL_NEXT_ID++))_$RANDOM | |
local _code=(" | |
Each-Do () { | |
unset -f Each-Do | |
local IT $_successful=() $_failed=() | |
for IT in \"\${${1:?required}[@]}\"; do | |
if ${Each_Do#*\)}; then | |
$_successful+=(\"\$IT\") | |
else | |
$_failed+=(\"\$IT\") | |
fi | |
done | |
") | |
local _name | |
if [[ $Success_Do ]]; then | |
_name=${Success_Do%% *} | |
_code+=(" | |
$_name () { | |
for IT in \"\${$_successful[@]}\"; do | |
${Success_Do#*\)} | |
done | |
} | |
$_name; unset -f $_name | |
") | |
unset -f Success-Do | |
elif [[ $Success ]]; then | |
eval " | |
${Success%\}} | |
if declare -F Each-Do >/dev/null; then | |
For-All $_successful | |
fi | |
}" | |
unset -f Success | |
_name=${Success%% *} | |
_code+=($_name "unset -f $_name") | |
fi | |
if [[ $Fail_Do ]]; then | |
_name=${Fail_Do%% *} | |
_code+=(" | |
$_name () { | |
for IT in \"\${$_failed[@]}\"; do | |
${Fail_Do#*\)} | |
done | |
} | |
$_name; unset -f $_name | |
") | |
unset -f Fail-Do | |
elif [[ $Fail ]]; then | |
eval " | |
${Fail%\}} | |
if declare -F Each-Do >/dev/null; then | |
For-All $_failed | |
fi | |
}" | |
unset -f Fail | |
_name=${Fail%% *} | |
_code+=($_name "unset -f $_name") | |
fi | |
_code+=(\}) | |
local _IFS=$IFS; IFS=$'\n' | |
eval "${_code[*]}"; IFS=$_IFS | |
# declare -f Each-Do | |
Each-Do | |
} | |
if [[ $BASH_SOURCE = "$0" ]]; then | |
main "$@" | |
fi | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Disclaimer: Use at your own risk. May contain bugs or hurt your brain. It's Bash.