관리 메뉴

HAMA 블로그

여러언어를 통해본 함수형 스타일 ( 함수포인터,함수자,람다 ) 본문

소프트웨어 사색

여러언어를 통해본 함수형 스타일 ( 함수포인터,함수자,람다 )

[하마] 이승현 (wowlsh93@gmail.com) 2015. 4. 27. 10:51

2편. 여러언어를 통해본 함수형 스타일 ( 함수포인터,함수자,람다 )


본글은 멀 주장하거나  전문적인 글이 아니라, 정보를 공유하기위한 모음글입니다. 
시간이 될때 관심이 있으면 편하게 읽어보시면 될듯합니다. 같이 익숙해져가는게 목표입니다.
먼가를 주장하는글은 1편 언어에서의 강력함과 대중성 그리고 스칼라 http://okky.kr/article/275634  에 있습니다. 참고로  함수형이라는 말에는 다양한 개념들이 있으나 이 글에서는 함수를 매개변수로 넘기는 스타일들부터 익숙해지자는것에 한정되있습니다.



함수 포인터의 정의는  전달가능한 행위의 시작위치! 라고 말할수있으며 사용이유로는  
아래와 같이 3가지정도를 말할수있을듯 합니다.  1번이 가장 핵심이구요 .
람다도 마찬가지로 지연호출이 핵심중 하나입니다.

1. 호출시점 유연화.
2.  struct 에서 행위를 포함할수있게함
3. 디펜던시 문제 해결.  
4. 코딩 단순화로 인한 사고 단순화 


예제  in C ) 

#include <stdio.h>

void apple(void)
{
  printf("apple");
};


int main()
{
  void (*fptr)(void);          // 선언
  fptr = apple;                // 대입
  fptr();                      // 호출
  someFunction(fptr);          // 매개변수로 넘김.
}
 fptr 는 행위 (애플이라고 프린팅하는) 를  가르키고있습니다.  
행위를  함수의 매개변수로 넘겨서 다른곳(someFunction)에서 그 행위에 대한 책임을 지도록  합니다.

예제  in C++ ) 

#include <iostream>
using namespace std;
class fruit
{
	public:
		void apple()
		{
			cout << "apple" << endl;
		}
		void berry()
		{
			cout << "berry" << endl;
		}
};

int main()
{
	fruit x, *y;
	void (fruit::*f)(void);    //  선언 
	
	f = &fruit::apple;         //  대입
	(x.*f)();                  //  호출

	f = &fruit::berry;
        y = new fruit;
	(y->*f)();

	delete y;
} 

C 에는 없는 클래스라는 개념을 가지고있는 C++ 
클래스의 멤버함수를 가르키는 방법입니다. 객체를 가르키는 방법에 따라 다르기때문에  복잡합니다.
헤깔리기때문에 보통 사용할때 구글링으로 확인합니다.


예제 in C++ with boost) 

class CHello
{
	void Say()
	{ 
		print(“hello”); 
        }
};

Void func1()
{
	CHello hello;                                             // CHello 의 객체 선언 
	boost::function< void ( void ) > memfunc;                 // boost::function 객체 선언
	memfunc = boost::bind( &CHello::DebugOut, hello, _1 );    // DebugOut 함수를 func 에 바인딩

        func2(memfunc);     
}     
                                                                      
Void func2(boost::function< void ( void ) >   _func)
{

    _func( 5 );     // CHello::Say() 가 호출!!!  	1.  행위를 하게함. ( observer 패턴)  
                    //                          2.  호출위치를 조절할수도 있고 
                    //                          3.  다른모듈에서 CHello를 호출.(디펜던시문제)

} 

위에는 클래스에 대한 멤버함수를 포인팅하는 방법을 좀더 쉽게 하기위해서
비표준 라이브러리에서  function과 bind가 사용되었습니다.근데 진짜 저게 더 쉬워진걸까?
역시 쓸때마다 구글링합니다. 

예제 in C++ 11) 

#include <iostream>
#include <functional>

using namespace std;

int add(int a, int b) {
    return a + b;
}

int main() {
    auto f = bind(add, 1, 2);
    cout << f() << "\n";
}

///////////////////////////////////////////////////////////////////////////////////

#include <iostream>
#include <functional>

using namespace std;
using namespace std:placeholders;

int add(int a, int b) {
    return a + b;
}

int main() {
    auto add1 = bind(add, 1, _1);
    cout << add1(100) << "\n";
}


자. c++11  에 와서 boost  라는 비표준라이브러리를 안쓰고 표준라이브러리를 사용하면서
조금 깔끔해졌습니다. auto의 등장 



예제  in C++  11 이전 ) 

boost::asio::ip::tcp::acceptor m_acceptor;
 
void handle_accept(Session* pSession, const boost::system::error_code& error)
{
  if (!error)
  {     
       std::cout << "클라이언트 접속 성공" << std::endl;
                   
       pSession->PostReceive();
  }
}
 
m_acceptor.async_accept( m_pSession->Socket(),
                         boost::bind(&TCP_Server::handle_accept,  // 함수 객체를 넘긴다. (함수포인터넘겨도됨)
                         this,
                         m_pSession,
                         boost::asio::placeholders::error)
                    );

예제 in C++ 11 이후) 


boost::asio::ip::tcp::acceptor m_acceptor;
 
 
m_acceptor.async_accept( m_pSession->Socket(),
                     [this](boost::system::error_code error)   // 람다식을 넘김!!
                    {
                       if (!error)
                       {  
                          std::cout << "클라이언트 접속 성공" << std::endl;
                          m_pSession->PostReceive();
                       }
                      
                       StartAccept();
                    }
                    );

C++도 11 버전 (2011년)  이전에는  함수자 or 서술자를 쓸때 그것들의 위치가 실제 사용되는 함수와 
거리가 멀어지는 문제점때문에 쓰기 꺼렸었지만, 11 버전 이후에 람다가 표준이 되면서 좀 더 많이 사용될것이라고 생각됩니다.


예제 in JAVA)    

Public void foo()
{
        final int n = 1;
	Class  ActionListener() 
	{
		Pubic void actionPerformed(Action e)
		{
			System.out.pringln(“Clicked! N =+n);
		}
	}
      ActionListener ac = new ActionListener() 
      
      
      Jbutton button = new Jbutton(“Click me”);
      button.addActionListener(ac);
}

자바 내부클래스입니다.
final int n = 1; 여기서 n 이 내부클래스에서 참조되는게 중요한데..
저게 바로 클로저라고 하죠. 내부에서 외부의 존재를 참조하는것!! 
다만 final 이 꼭 붙어야해서 클로저가 아니다라는 말도 합니다.


예제 in JAVA)   익명클래스 

Public void foo()
{
	final int n = 1;
      Jbutton button = new Jbutton(“Click me”);
      button.addActionListener(new ActionListener() 
				{
					Pubic void actionPerformed(Action e)
					{
						System.out.pringln(“Clicked! N =+n);
					}
			});
}




ActionListener 클래스가 함수매개변수로 바로 만들어져 삽입된다.


예제 in JAVA 8 with lamda ) 


Public void foo()
{
      final int n = 1;
      Jbutton button = new Jbutton(“Click me”);
      button.addActionListener(  ()-> System.out.pringln(“Clicked! N =+n);
			      );
} 

자바는 함수를 따로 만드는게 언어철학차원에서 지원하지 않기때문에 C++보다 내부 구현에 복잡해질 여지가 많아보입니다. 어설프게 타언어 장점 가져올바에는 일관성을 지키는게 훨씬 낫겠지요.

예제 in C# with delegate) 

          //대리자 선언
          public delegate void SayHandler(string mag);
                
            [1]익명메서드 : Say 함수를 작성하지 않고 익명메서드로 작성
            SayHandler hi = delegate(string msg)
            {
                Console.WriteLine(msg);
            };
            hi("익명메서드");

            //[2]익명메서드 : Button의 click event를 익명 메서드로 작성
            Button button = new Button();
            button.Click += delegate(string msg) 
            {
                Console.WriteLine(msg);
            };
            button.OnClick("이벤트 익명메서드");


            //[3]람다표현식 : Button의 click event를 Ramda 표현식으로 작성
            Button button1 = new Button();
            button1.Click += (string msg) => Console.WriteLine(msg);
 
            button.OnClick("람다 표현식");
           
            

 C# 도 1버전부터 4버전까지 계속 대리자(함수포인터 , 람다) 가 발전되어져 왔다.

예제 in Scala  with lamda) 

스칼라에서 함수표현 

def max(m: Int, n: Int): Int = if(m > n) m else n

스칼라에서의 람다식

1) 보통 

def bubbleSort(arr: Array[Int], order: (Int, Int) => Boolean): Unit {
    ...
    val o: Boolean = order(a, b)
    ...
}

val arr: Array[Int] = Array(2, 5, 1, 7, 8)
bubbleSort(arr, (a: Int, b: Int) => a > b)

2) 짧게 
val arr = Array(2, 5, 1, 7, 8)
bubbleSort(arr, (a, b) => a > b)


3) 더 짧게
val arr = Array(2, 5, 1, 7, 8)
bubbleSort(arr, (_: Int) > (_: Int))

4) 가장 짧게
val arr = Array(2, 5, 1, 7, 8)
bubbleSort(arr, _ > _)


 예제  in Scala vs JAVA8   with lamda) 


JAVA8  ) 
List names = Arrays.asList("1", "2", "3");
Stream lengths = names.stream().map(name -> name.length());

Scala ) 
val names = List("1", "2", "3")
val lengths = names.map(name => name.length)



JAVA8  )
List<Photo> photos = Arrays.asList(...)
List<Photo> output = photos.filter(p -> p.getSizeInKb() < 10)

Scala ) 
val photos = List(...)
val output = photos.filter(p => p.sizeKb < 10)


예제 in javascript)


function applyOperation(a, b, operation) 
{
  return operation(a,b);
}

function add(a,b) { return a+ b; }

applyOperation(1,2, add);


// anonymous inline function
applyOperation(4,7, function(a,b) {return a * b})


예제 in Ruby with lamda)

class Array
  def iterate!(code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end

array = [1, 2, 3, 4]

array.iterate!(lambda { |n| n ** 2 })

puts array.inspect

# => [1, 4, 9, 16]


예제  in JAVA )   12보다 큰 단어의 워드카운팅.


int count = 0;
for(String w : words){
   if (w.length > 12 ) count++;
}
머하는지 일단 읽어봐야하고 혹시 index 증감으로 배열참조하다보면 오류생길가능성도 조금 더 있겠지만 
깔끔하고 너무나 익숙하다.  


예제  in JAVA 8 with lamda )  

long count = words.stream().filter( w -> w.length() > 12 ).count();
람다를 이용하여 한줄에 작성되었다. 이거 좋다고 이렇게 쓰자고 난리다. 
다양한 알고리즘도 미리 제공해주고 있다. 흠 근데 아직 잘 모르겠다. 복잡한거 같다. 패스~


예제 in JAVA  8 with lamda and parallel )

long count = words.parallelStream().filter ( w -> w.length() > 12).count()

어라, 병렬을 한방에 해주네??  이 정도면 끌리는데? 어려워도 배워볼만하네.
이게 추상화의 장점이구나!! 기능추가의 자유도가 쉽게 높아지는군!

만약 저걸 하둡의 맵 or 리듀스공정에 사용하면 손쉽게 분산병렬과 쓰레드레벨 병렬의 시너지가
날듯합니다.( 여기에 추가적으로 GPU레벨 믹스도 추상화가 높아지면 쉬워지겠네요. )

참고로 c++ 은 저렇게 언어 차원에서 저런식의 병렬을 지원하지 않으며 openMP 나 PPL 같은
외부 라이브러리를 통해  지원합니다.


Comments