Programming Paradigm

SeungHyuk Shin·2021년 4월 13일
0

프로그래밍 패러다임(programming paradigm)은 프로그래밍의 패러다임 형태이다. 소프트웨어 공학을 할 때의 패러다임 형태인 방법론과 비교된다.

프로그래밍 패러다임은 프로그래머에게 프로그래밍의 관점을 갖게 해 주고, 결정하는 역할을 한다.

  • 명령형 프로그래밍
    • 절차적 프로그래밍
    • 객체 지향 프로그래밍
  • 선언형 프로그래밍
    • 함수형프로그래밍

1. 명령형 프로그래밍(Imperative programming)


명령형 프로그램은 선언형 프로그래밍과 반대되는 개념으로, 프로그래밍의 상태와 상태를 변경 시키는 구문의 관점에서 연산을 설명하는 프로그래밍 패러다임의 일종이다. 실제 우리가 쓰는 말에서 명령이 어떤 동작을 할것인지를 명령으로 표현 하듯이, 명령형 프로그램은 컴퓨터가 수행할 명령들을 순서대로 써 놓은 것이다.

각각의 단계의 지시 사항들이 있고, 상태라는 것은 현실 세계에 반영된다. 명령형 프로그래밍의 기본 생각이 개념적으로 친밀하고, 직접적으로 구체화되어 있어서, 대부분의 프로그래밍 언어들은 명령형이다.

명령형 프로그래밍의 방법으로는 크게 두가지 절차지향과 객체지향이 있다.

1-1. 절차지향 프로그래밍(Procedural Programming)

절차지향은 폭포가 위에서 아래로 흐르는 것처럼 Top-Down 방식의 순차적인 처리가 중요시 된다. 프로그램 전체가 유기적으로 연결 되도록 만드는 프로그래밍 기법이다. 이는 컴퓨터의 작업 처리 방식과 유사하기에 객체 지향 언어를 사용하는 것에 비해 더 빨리 처리되어 시간적으로 유리하다. 객체지향 프로그래밍과의 가장 큰 차이점은 데이터와 함수를 별개로 취급한다는 것이다.

1-2. 객체지향 프로그래밍(Object-Oriented Programming)

객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다. 따라서 절차지향과는 다른 Bottom-Up 접근 방식을 사용한다.

객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다. 또한 프로그래밍을 더 배우기 쉽게 하고 소프트웨어 개발과 보수를 간편하게 하며, 보다 직관적인 코드 분석을 가능하게 하는 장점을 갖고 있다.

객체 지향 프로그래밍의 치명적인 단점은 함수형 프로그래밍 패러다임의 등장 배경을 통해서 알 수 있다. 바로 객체가 상태를 갖는다는 것이다. 변수가 존재하고 이 변수를 통해 객체가 예측할 수 없는 상태를 갖게 되어 애플리케이션 내부에서 버그를 발생시킨다는 것이다. 이러한 이유로 함수형 패러다임이 주목받고 있다.

출처

1-3. 절차지향 vs 객체지향

그렇다면 두 프로그래밍 방법의 가장 큰 차이는 위에서 설명했듯이 동일한 문제를 풀기위해 객체지향은 프로그램을 작은 객체들로 분리했다면, 절차지향은 프로그램을 작은 프로시져나 함수로 분리했다는 것이다.

즉 절차지향은 데이터를 중심으로 프로시저,함수와 그에 대한 입출력을 다루는 대신 객체지향은 기능을 중심으로 한 객체를 다룬다. 연산은 객체에게 내부 프로시저 중에 하나를 수행하라고 요청하는 것으로 이루어지며 이런 방법으로 하여 내부 상태를 다룬다.

2. 선언형 프로그래밍


명령형 프로그래밍이 어떻게(HOW) 하는 것에 중점을 두었다면 선언형 프로그래밍은 무엇(WHAT)인지에 대해 중점을 두었다고 생각하면된다. 즉 선언형 프로그래밍의 목표는 어떻게 정답에 도달하는지는 관심이 없다.

2-1. 함수형 프로그래밍

사실 이 글은 쓰는 이유가 함수형 프로그래밍을 정리하기위해 작성한다 해도 과언이 아니다. 그럼 더 나아가 함수형 프로그래밍에 대해 알아보자. 함수형 프로그래밍은 언어나 방식을 배우는 것이 아닌 함수로 프로그래밍적인 사고를 하는 것이다. 함수형 프로그래밍을 설명하기전에 우선 순수 함수에 대해 알아보자.

순수함수

수학에서 f(x) = y는 입력값 x에 대해서 항상 y를 유지한다는 개념을 프로그래밍으로 옮긴 것이 순수함수이다. 순수함수는 동일 입력시 둥일 출력을 보장하며 부수 효과가 없다.

def sum1(x,y):
	return x+y 
    #순수함수
    
val = 5 
#val 값에 따라 sum2의 값이 바뀜

def sum2(x,y):
	return x+y+val 
    #순수함수가 아니다

def sum3(x,y):
	val = y
    return x+y 
    #결과 값은 항상 같으나 부수효과가 있음
    

따라서 이처럼 함수의 반환값이 아닌 외부상태에 영향을 주거나 받는 함수들은 순수함수가 아니다. 이런 상황은 전역변수 뿐만 아니라 파일불러오기,네트워크 fetching, 예외처리 등의 경우에도 생길 수 있다.

함수형 프로그래밍의 특징

함수형 프로그래밍의 특징으로 총 4가지가 있다.

  • 불변성
  • 참조 투명성
  • 일급 함수
  • 게으른 평가

불변성

불변성은 어떤 값의 상태를(메모리에 이미 담긴 상태를) 변경하지 않는다는 뜻이다. 상태의 변경은 부수 효과를 일으키기 때문에, 함수형 프로그래밍에서는 이를 제한한다. 따라서 한번 정의한 객체의 값은 절대 바뀌어서는 안된다.

정확히 말하면 메모리에 저장된 값을 변경하는 모든 행위를 의미하며, 여기에 변수의 재할당과 같은 행위도 포함되는 것이다.

값에 의한 호출 vs 참조에 의한 호출

참조 투명성

참조 투명성은 프로그램의 변경 없이도 어떤 표현식을 값으로 대체할 수 있다는 뜻이다. 즉 함수 f(x)가 y를 반환할 때, f(x)는 y로 대체할 수 있다라고 말 할 수 있다.

name = 'shin'

def hello():
	print("hello, ${name}")

위 hello() 함수는 print() 및 name 외부의 값들을 참조하고 있어서 참조에 투명하지 않다. name 이 바뀌면 hello() 의 값도 자연스레 변경되기 때문이다

def hello(name):
	return "hello, ${name}"
    
STRING = hello('shin')
print(STRING)

#파이썬은 상수 개념이 없어 대문자로 대신했다

이제 hello() 함수가 항상 일관적으로 반환해서, 참조에 투명한 함수가 되었다.

일급 함수 First-class Function

일급 함수를 알아보기 전에, 일급 시민을 알아보자. 우선 프로그래밍 언어에서 어떠한 대상이 일급 시민이 되려면, 다음 세 가지 조건을 만족해야 한다.

  • 대상을 함수의 매개변수로 넘길 수 있다.
  • 대상을 함수의 반환값으로 돌려줄 수 있다.
  • 대상을 변수나 자료구조에 담을 수 있다.

여기에서 대상을 객체로 바꾸면 일급 객체의 조건이 되고 대상을 함수로 바꾸면 일급 함수의 조건이 된다.

  • 함수를 함수의 매개변수로 넘길 수 있다.
  • 함수를 함수의 반환값으로 돌려줄 수 있다.
  • 함수를 변수나 자료구조에 담을 수 있다.
def sum(x,y):
 	return x+y
    
def send(method):
	return method(1,2)
    
A = sum
B = send(A)
print(B)
# B = 3, 모든 조건을 만족하는 일급함수다.

게으른 평가 Lazy Evaluation

일반적인 언어는 코드의 실행 즉시 값을 평가(Eager Evaluation)하지만 함수형 언어에서는 값이 필요한 시점에 평가(Lazy Evaluation)된다. 값이 실제로 필요한 시점까지 실행하지 않기 때문에 시간이 오래 걸리는 작업도 손쉽게 동작시킬 수 있다.

import time

L = [1, 2, 3]

def print_iter(iter):
	for element in iter:
    		print(element)
            
def lazy_return(num):
	print("sleep 1s")
    	time.sleep(1)
    	return num

print("comprehension_list=")
comprehension_list = [ lazy_return(i) for i in L ]
print_iter(comprehension_list)

print("generator_exp=")
generator_exp = ( lazy_return(i) for in L )
print_iter(generator_exp)

결과를 보면 comprehension을 사용했을 때는 'lazy_reuturn' 함수를 미리 3번 실행하여 값을 만들어 놓고 한번에 출력한다.
즉, 라면을 미리 주문받은 만큼 끓여놓고 한번에 손님들에게 나가는 방식이다.

두번째로 lazy evaluation 방식인 generator를 사용하면 generator_exp 값을 사용하는 순간에만 함수를 수행하게 된다.
라면을 손님이 오면 그 때 라면을 끓여서 제공하는 방식이다.

3. 명령형 vs 선언형



앞서 말했듯이 명령형 프로그래밍은 어떻게 할 것인가에 가깝고, 선언형 프로그래밍은 무엇을 할 것인가와 가깝다.

예를 들어 친구에게 샌드위치를 만들어 달라고 부탁한다 생각해보자.

  • 명령형 방식(HOW) : 빵 두개를 준비해서, 각종 야채와 햄을 준비해 양쪽에 빵을 덮으면 돼.
  • 선언형 방식(WHAT) : 샌드위치 2개만 만들어줘

위의 예시를 보면 알겠지만 명령형 방식은 친구가 "어떻게" 샌드위치를 만들지에 대해 알려주고 있다. 따라서 "어떻게" 샌드위치를 만들지에 대한 단계를 하나하나 나열해야 한다. 그와 반대로 선언형 방식은 내가 "무엇을" 원하는지에 더 집중되어 있다.

알수 있는 사실은 선언형의 경우 친구가 샌드위치를 어떻게 만드지는에 대해 이미 알고 있어야 한다는 것이다. 사실 선언형 방식이 제대로 동작하기 위해서는 명령형으로 "어떻게"가 구현된 것들이 추상화되어 있어야 한다.

위에서 살펴본 선언형 예시들에서도 명령형 방식이 추상화된 부분을 찾아볼 수 있다.

0개의 댓글