I am having trouble understanding the concept of 'good' functional composability. Assume the following approaches for deleting files with a specific extension:
Using Lambda functions:
def traverse_tree(
temp_storage: Local, extensions: List[str], is_raw: bool, operation: Callable
):
for folder_path, subfolders, filenames in os.walk(temp_storage.root_path(is_raw)):
for filename in filenames:
base, ext = os.path.splitext(filename)
if ext in extensions:
file_path = os.path.join(folder_path, filename)
operation(file_path)
def clear_temp(
temp_storage, extensions: List[str], is_raw
):
traverse_tree(temp_storage=temp_storage, extensions=extensions, is_raw=True, operation=lambda x: os.remove(x))
Using an iterator:
def traverse_tree(
temp_storage, extensions: List[str], is_raw
):
for folder_path, subfolders, filenames in os.walk(temp_storage.root_path(is_raw)):
for filename in filenames:
base, ext = os.path.split(filename)
if ext in extensions:
file_path = os.path.join(folder_path, filename)
yield file_path
def clear_temp(
temp_storage: Local, extensions: List[str], is_raw: bool
):
for path in traverse_tree(temp_storage, extensions, is_raw):
os.remove(path)
Is one approach favored over the other in terms of modularity and ease of debugging? If so, can you give an example where one approach objectively results in less flexibility?
Thanks,
Your question has nothing to do with functional composability - you aren't composing functions.
It seems to be that you are having a hard time justifying whether
operation: Callable
is even needed and what good use it has (if any at all). The problem with your example is that it is oversimplified. How would you add renaming of (some) files matching the arbitrary complex pattern? Would you copy/paste another version of literally the same code with the only difference in the body of thefor
loop that materializes theiterator
?One (pretty common and justified by practice) way of achieving it is extracting a higher-order function out of it and taking it as an argument. Such a trick does not work well in languages like good old C where you can't have a well-typed (if only C had some well-typed-ness...) function as a first-class object and have no anonymous functions (also known as "lambdas" - a term that came from the lambda calculus). But Python does have it - as well as many other modern languages do!
With that bit, you can reuse the same piece of code and make it kinda polymorphic up to the implementation of all the (function) arguments it takes. More to say, this is exactly how widely recognized "purely functional" programming languages like Haskell structure the codebase, in particular - their standard libraries. Another good example of this approach is Python's
filter
andmap
- both expect you to provide a function that does the filtering/mapping such that a single implementation could have taken a good care over iteration itself.