FP example: while

김장훈·2022년 5월 12일
0

fp-with-python

목록 보기
3/3

Functional Programming 사용에 대한 여러 예시를 작성

1. 일반적인 while 형태

  • while 이 돌면서 중간에서 무언가 하는 형태이다.
  • threshold 를 받아서 횟수 만큼 돈다.
  • loop 제어를 위해서 index 를 조절해줘야한다.
def sample_function(index: int):
    return index - 1
    
def sample_main(threshold: int):
    res = []

    start_index = 1
    while True:
        res_index = sample_function(start_index)
        if res_index % 2 == 0:
            res.append(res_index)

        if len(res) >= threshold:
            break
        start_index += 1
    return res

2. 꼬리재귀형태

  • 꼬리재귀란? 쉽게 생각하면 return 에 추가 연산 없이 자신 그 자체를 return 하는 형태
  • python 은 recur 관련 issue 가 있다고 알고는 있으나 여기서는 다루지 않음

def sample_function(index: int):
    return index - 1


def sample_main_two(start_index, basket: list = None, threshold=10):
    if not basket:
        basket = []

    if len(basket) >= threshold:
        return basket

    res_index = sample_function(start_index)
    if res_index % 2 == 0:
        basket.append(res_index)

    return sample_main_two(start_index + 1, basket, threshold)
  • 기존과 코드량 자체는 크게 변하지 않았고 while 이 제거되기만 하였다.
  • FP 측면에서 이 정도도 충분해보이긴 한다.

3. 꼬리 재귀형태 + pipe

  • 억지로라도 pipe 형태를 집어넣은 형태
  • go 함수를 사용하기 위해선 curry 가 필수이다.
  • take_while 등을 활용하여 pipe line 자체를 recur 하게 구현해보려 했으나 이전 값을 계속 전달해주는 것을 어떻게 구현해야할지 모르는 점 + 오히려 가독성이 떨어진다 판단하여 멈춤
  • 쉽게 얘기하면 구현 할 방법을 모르겠어서 못함 .....

def sample_function(index: int):
    return index - 1
    
@curry
def update_list(origin_res: list, new_list: list):
    return origin_res + new_list


def sample_main_three(start_index, basket: list = []):

    if len(basket) >= 10:
        return basket

    basket = go(
        sample_function(start_index + 1),
        custom_filter(lambda x: x % 2 == 0),
        update_list(basket),
    )

    return sample_main_three(start_index + 1, basket)
  • 여기서 하나 변경점이 추가로 더 발생하였는데 sample_function 의 경우 iter 를 return 하지 않는다.
  • go pipe line 은 모두 iterable 을 대상으로 작동한다.
  • 따라서 sample_function 을 list 로 return 하거나 custom_filter 를 변경해야한다.
  • 값 하나 주는 function 을 list 로 다루는 것은 어색하므로 custom_filter 를 아래와 같이 변경해주었다.
@curry
def custom_filter(func: Callable, iterable: iter):
    if not isinstance(iterable, list):
        print(
            f"[Warning] input_iter is not a list, input: {iterable}, {type(iterable)}"
        )
        iterable = [iterable]

    res = []
    for value in iterable:
        if func(value):
            res.append(value)
    return res

test code

# pytest -k test_while_with_count_should_work -s

def test_while_with_count_should_work():
    base_counts = 10
    target = list(range(0, base_counts + base_counts, 2))
	print(f"target: {target}")
    assert sample_main(base_counts) == target
    assert sample_main_two(0) == target
    assert sample_main_three(0) == target
tests/test_fp.py target: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
[Warning] input_iter is not a list, input: 0, <class 'int'>
[Warning] input_iter is not a list, input: 1, <class 'int'>
[Warning] input_iter is not a list, input: 2, <class 'int'>
[Warning] input_iter is not a list, input: 3, <class 'int'>
[Warning] input_iter is not a list, input: 4, <class 'int'>
[Warning] input_iter is not a list, input: 5, <class 'int'>
[Warning] input_iter is not a list, input: 6, <class 'int'>
[Warning] input_iter is not a list, input: 7, <class 'int'>
[Warning] input_iter is not a list, input: 8, <class 'int'>
[Warning] input_iter is not a list, input: 9, <class 'int'>
[Warning] input_iter is not a list, input: 10, <class 'int'>
[Warning] input_iter is not a list, input: 11, <class 'int'>
[Warning] input_iter is not a list, input: 12, <class 'int'>
[Warning] input_iter is not a list, input: 13, <class 'int'>
[Warning] input_iter is not a list, input: 14, <class 'int'>
[Warning] input_iter is not a list, input: 15, <class 'int'>
[Warning] input_iter is not a list, input: 16, <class 'int'>
[Warning] input_iter is not a list, input: 17, <class 'int'>
[Warning] input_iter is not a list, input: 18, <class 'int'>
  • log가 끔직하지만 일단 이정도로...
profile
읽기 좋은 code란 무엇인가 고민하는 백엔드 개발자 입니다.

0개의 댓글