brunch

You can make anything
by writing

C.S.Lewis

by 이남주 NJ Namju Lee May 10. 2020

함수(Function) 디자인 그리고 Safeguard

Computational Design & Creative Coding

Function(함수) 그리고 Safeguard를 어떻게 디자인할까요?


다음과 같은 질문에서 시작해 봐요

질문 링크


 그라스하퍼 파이썬 프로그래밍에서, 왜 인풋들을 int(숫자)로 cast(변환)를 해야 하나요? 그라스 하퍼 컴포넌트에서는 숫자로 인식하는데 파이썬에서는 문자로 들어오나요? 


답을 해보면, 

내부적으로 데이터 타입을 확인하고 케스트를 할 거예요. 대부분 펑션을 디자인할 때 그렇게 많이 해요. 흔히 safeguard를 쳐 놓는 거죠. 그라스하퍼 컴포넌트도 자동으로 케스트를 해주는 상황이 많아요.

그라스하퍼 플러그인을 만들 때도, 인풋 데이터를 확인해서 케스트 해주는 경우도 많아요, 가령 Vector3d가 명백한 인풋인데 Point 3d를 넣었을 때, 문제없이 작동하는 원리가 그러해요. 좀 더 자세히 설명하면, 상위 클래스인 Geometry로 인풋을 받고 Point3d일 경우에는 Vector3d로 캐스트를 해서 주어진 계산을 문제없이 이행하는 것이죠.

컴퓨터 언어는 굳이 말하면, 로우 레벨로 갈수록 데이터 타입에 민감합니다. 그래서 제가 강조하는 것이 하이 레벨로만 이해하고만 있으면 안 된다는 거예요. 나중에 이상하게 이야기할 수 있거든요. 뒷단에서 어떻게 도는 지를 이해해야 해요. 별거 아닌 거 같지만, 매우 중요한 거예요. 

따라서, 제 생각에는, “그래스호퍼의 definition은 panel 내에 있는 숫자를 숫자로 받는데 파이썬은 문자로 받는군요” 보다는, “그래스호퍼의 definition은 panel내에 있는 숫자나 문자를 자동으로 펑션에 맞게 케스트를 해주는 세이프가드가 들어가 있네요. 따라서 저도 타입을 체크해서 그거에 맞게 펑션을 디자인해야겠네요” 가 좀 더 좋아 보여요.

그라스하퍼의 패널 컴포넌트의 리턴 값은 문자형으로 확인돼요.  가령, 파이썬에서

def myFunction(data):  
    if data == 'null':   
        # do something  
    elif data == 'undefine':    
        # do something  
    elif data == 'string':    
        # do something  
    else :   
    # do the thing  

이런 식으로 실제 계산을 이행하기 앞서 데이터가 validate 한 지 안 한 지를 체크하는 거죠. 그리고 실제 기능을 수행하는 것이죠, 이런 장치가 없으면 프로그램이 작동이 안 돼요. 만약 그래스하퍼가 이런 세이프가드가 없었으면 이러한 이해가 없는 사용자들이 엄청 고생을 하고 중간에 그만두겠죠.



개념적으로, 우리가 먼저 생각해야 할 사항들은, (1) 프로그래밍의 패러다임 (2) 함수(function)의 종류를 이야기해보아요.

Programming Paradigms

다양한 프로그램의 패러다임이 존재하고(https://en.wikipedia.org/wiki/Programming_paradigm), 새롭게 개념들도 제안되고, 사라지고, 통합되고, 강화되고 하면서 개념들을 발전해 나가겠죠. 여러분들이 익숙한 절차적 프로그래밍(procedural), 객체지향 프로그래밍 (object-oriented ), 혹은 함수형 프로그래밍(functional)이 그 예일 수 있어요. 이것들은 개념이에요. 따라서 프로그램 언어에 따라서, 다양한 프로그래밍 패러다임을 지원하고 혹은 부분적으로 지원하는 경우도 있죠.


일반적으로, C, C++의 경우는 절차 지향 프로그래밍, C++, JAVA, C#, Python의 경우는 객체지향 프로그래밍

Lisp, Javascript의 경우는 함수형 프로그램으로 볼 수 있어요. 물론 그 안에서 다양한 패러다임을 사용할 수 있죠. 가령 Python을 하더라도, 함수형 스타일로 코딩을 할 수 있다는 거예요.


프로그램의 패러다임은 개념에 대한 이야기고, 각각의 언어 단에서 구현될 때는 칼로 두부를 자르듯이 단순하게 구현되지는 않을 수 있어요. 따라서, 프로그램의 패러다임과 장단점을 이해하는 것은 좀 더, 언어를 활용할 때, 체계적이고, 올바른 컴퓨테이셔널 한 사고를 이행하는데 도움을 받을 수 있어요.


함수(function)

그렇다면 두 번째로, 함수(function)에 대한 개념을 하나 더 이야기해 보면, function의 종류로서, pure function (https://en.wikipedia.org/wiki/Pure_function) 이 있죠. Impure function이 있어요. 몇 가지 원칙들이 존재하는데요, 어떠한 사이드 이펙트(side effects) 없이 주어진 인풋에 상응하는 아웃풋을 내주면 pure functino이라고 할 수 있어요. 더 쉬운 예를 들면, 


int Square(int x){

    return x * x

}


이처럼 인풋(input)에 상응하는 정해진 값을 리턴해주는 것을 pure function이라고 볼 수 있죠. 반대 예로 클래스 안에서 어떤 업데이트를 하면서 내부적인 혹은 글로벌 변수를 업데이트를 하는 함수면 pure function으로 볼 수 없어요. 


이러한 규칙을 따름으로서 펑션의 역할과 사이드 이펙트를 이해하고 조절해서, 모듈화를 할 수 있게 도와주는 것이죠. 이것들은 개념이기 때문에 반드시 따라야 프로그램이 실행되고 안되고 하지는 않지만, 약속이고 가이드라인이기 때문에 이해하고 부분적 혹은 전체적으로 적용을 해서 원하는 디자인 알고리즘을 만들어 가는 것이죠.



제네릭 프로그래밍


우선 제네릭 프로그래밍(https://en.wikipedia.org/wiki/Generic_programming)에 대해서 생각해 봐요. 

평균을 계산해주는 function을 만든다고 가정하면,  숫자는 int, float, double 등 다양한 타입이 존재하죠. 우리 인간이 봤을 때는 다 같은 숫자지만, 컴퓨터는 전혀 다른 개념으로 받아 드리죠. 그래서 위의 펑션을 3가지로 따로따로 만들어 줘야겠죠.


// c# implmentation

int Square(int number){

  return number * number;


float Square(float number){

  return number * number;


double Square(double number){

  return number * number;


위의 경우 3개의 다른 타입에 맞는 함수들을 각각 만들어 줘야겠죠. 하지만 위의 함수를 좀 더 일반화시켜 만들 수 있어요.


  T Square<T>(T number){      

    return number * number;


T는 타입을 런타임에 선언해서 그 타입에 맞게 함수가 작동하는 것이죠, 물론 위의 코드의 내부에는 타입을 체크하고 그에 따른 계산을 이행해 줘야 합니다. 일단 개념은 저러하죠.


만약 타입이 암묵적이다면?

이번에는 파이썬과 같이 타입을 명시적으로 지정하지 않아도 되는 typeless 언어 중 javascript의 예를 들어 볼까요? 하지만 적용은 모든 프로그래밍 언어들에서 가능합니다.


// javascript implmentation

const square= (number) => {

    return number * number;

}


위와 같이 함수를 선언했다면, 타입이 없기 때문에 그에 따른 결괏값을 장담할 수 없는 상황이 생길 수 있어요. 이런 경우에는 함수 안에서 타입들을 체크해서 그에 맞는 결과를 주는 것이죠. 


// javascript implmentation

const square= (number) => {

    if(number === undefined) {

      return 'cannot compute, number needed'

    }

    if(typeof number === 'string' ) {

      const num = parseFloat(number);

      if(num) {

        return number * number;

      } else {

        return 'cannot compute, number needed'

      }

    }

    return number * number;

}


console.log(square(3));            //  9

console.log(square('3'));          //  9

console.log(square('three'));  // cannot compute, number neede

console.log(square());              // cannot compute, number neede


Codepen: 예제 링크


결론적으로,

디자인 알고리즘을 짜다 보면, 다양한 깊이의 레벨로 컴퓨테이션을 추상화를 할 수 있어요. 흔히 프로그램의 아키텍처를 한다 파이프라인을 만든다 라고도 볼 수 있는데요. 이런 레벨들을 디자인할 때, 가장 밑단에서 돌아가는 함수(layer 0)가 있고, 그 위에서 그 함수들을 연결하는 함수(layer 1)가 있다고 가정해 본다면, 윗 레이어에 위치한 함수에서 인풋의 파라미터들이 유효한지를 검사하고 유효하다면 그 밑단에 위치한 함수들을 call 함으로써 계산을 수행할 수 있게 되죠. 따라서 layer 0 레벨에 위치한 함수들은 따로 파라메터를 체크할 필요없이 그 이행에만 충실하면 더 깔끔하고 직관적인 코드를 작성할 수 있겠죠. 


그렇게 하면 디버깅도 편하고, 모듈화도 편하게 할 수 있고, 좀 더 지속 가능한 디자인 툴을 만들 수 있게 되는 것이에요.



감사합니다.

Revision 2020/4/29

질문이나, 수정사항은 댓글로~







브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari