JS-Lambda 자바스크립트 라이브러리를 공개합니다.


JavaScript Array Extensions 자바스크립트 오픈 소스를 개발한 데 이어 JS-LambdaLGPL 라이센스로 공개합니다.

JavaScript 에서 람다 표현식(Lambda Expression)을 사용할 수 있도록 만든 라이브러리 입니다. 자세한 내용은 아래의 소스 코드를 참고 하시면 됩니다.

Github: https://github.com/powerumc/js-lambda

JS Lambda

  • It is possible lambda expression that can be used JavaScript.
  • you just got a function F();

Simple Examples

// Before  
function func(a,b) {
    return a + b;
}  
console.info( func(4,6) );


// ** After with JS-Lambda **  
var func = F("a,b => a + b");  
console.info( func(4,6) );  

// Result  
10

Or you can invoke directly

// Before  
function anonymousMethod(a,b) {
    return a + b;
}  
console.info( anonymousMethod(4,6) );  

// ** After with JS-Lambda **  
console.info( F("a,b => a + b")(4,6) );  

// Result  
10

Callback Examples

// Before  
function callback( func ) {
    if( func ) func();
}  

callback( function() { console.info('My name is Junil Um'); } );  

// ** After with JS-Lambda **  
callback(  F("() => console.info('My name is Junil Um');")  );  

// Result  
My name is Junil Um  

With jQuery

// Before  
var li = $("item li");  

li.each( function(i, o) {
    $(o).addClass("some");
} );


// ** After with JS-Lambda **  
var li = $("item li");  

li.each( F("(i, o) => $(o).addClass('some');") );  


Posted by 땡초 POWERUMC

댓글을 달아 주세요

요즘 참 할일도 많은데 할 수 있는 일이 점점 줄어든다. 필자는 블로그 버킷 리스트(bucket list)를 작성하는데 블로그가 사망하기 전에 꼭 해야 할 일을 목록으로 만들어 놓고 하나 하나씩 글을 써 나간다. 근데 할 일이 늘어만 간다. ㅠ

  • 당장 쓸 수 있는 글 39개
    사소한 개발 기술부터 심도있는 내용으로 흐리멍텅한 개념을 글을 쓰면서 잡아 나가는 것들

  • 개발 후 산출물로 쓸 글 37개
    오픈소스로 내놓을 계획, 또는 알고 있는 것들에 대한 증명이 필요하고 그 후에 쓸 수 있는 글

  • 연구개발 11개
    배우고 싶은 것, 하고 싶은 것, 해야 하는 것들이고 공부해야 쓸 수 있는 글들

아무튼 점점 쓸 것들이 늘어만 가지만, 하나 하나 하다보면 쓸게 없어 지는 날이 올거라 믿는다 >.,<

#1 - Umc.Core 프레임워크 다이나믹 프록시(Dynamic Proxy)

다이나믹 프록시(Dynamic Proxy)는 최근 IoC(Inversion of Controls)라는 개념으로 확장되고 객체지향 프로그래밍(OOP)의 효율성을 극대화한다. 다이나믹 프록시 기법은 이미 오래 전부터 존재해 왔다.

C++의 스마트 포인터(Smart Pointer) 처럼 직접 포인터를 건들이지 않고 레퍼런스 카운팅이나 객체의 생명주기를 프록시 개체(Proxy Object)를 통해 관리하는 기법이 있고, RPC(Remote Procedure Call)과 같은 원격 프로시저 호출을 위해 프록시 패턴(Proxy Pattern)이 기반에 깔려 직접 원격 구현을 숨기고 간단한 인터페이스만 제공해 주는 패턴 등 매우 다양하다.

최근의 다이나믹 프록시(Dynamic Proxy)는 IoC(Inversion of Control), DI(Dependency Injection), ORM(Object Relation Mapping), AOP(Aspect Oriented Programming) 등 객체를 다루는 프레임워크에서 기본적으로 채용할 만큼 객체를 다룸에 있어 가장 기본적인 기법 또는 기술에 속한다.

필자가 오늘 다루어 볼 내용은 이런 프록시 패턴(Proxy Pattern)이 기반이 되는 개체 제어를 위한 기법이다.

먼저 다이나믹 프록시(Dynamic Proxy)에 대해 예전에 잠깐 쓴 글을 참고하자.

과거 2009년도 닷넷엑스퍼트 재직 시절에 다이나믹 프록시(Dynamic Proxy)를 만들었다가, 2011년 좀 더 나은 객체 디자인으로 완전히 다시 만든 다이나믹 프록시(Dynamic Proxy) 프레임워크가 지금 설명할 Umc.Core.Dynamic.Proxy 이다.

Umc Core 프레임워크는 오픈 소스로 공개되어 있으며 누구든지 소스 코드를 열람할 수 있다. 현재 소스 코드는 다음의 링크를 통해 제공된다.

그 외에 필자가 공개한 소스 코드도 여럿 있으니 관심있는 분들은 참고하기 바란다.

 

 







#2 - 코드부터 보자.

만약 관리 언어(Managed Language)의 동작 매커니즘을 이해하지 못했다면 마치 마법과도 같은 기법으로 보일 것이다. 아래의 코드는 완벽하게 돌아가는 소스 코드이다.

using System;  
using Umc.Core.Dynamic;  

namespace ConsoleApplication1
{  
public interface IPerson
{
    string Name { get; set; }
    string Email { get; set; }
}  

class Program
{
    static void Main(string[] args)
    {
        var type   = DynamicProxyObject.InterfaceImplementationType<IPerson>();
        var person = (IPerson)Activator.CreateInstance(type);

        person.Name  = "POWERUMC";
        person.Email = "powerumc at 지멜";

        Console.WriteLine("Name : {0}, Email : {1}", person.Name, person.Email);
    }
}
}  

한번 위의 코드를 보자. IPerson 인터페이스는 IPerson을 구현하는 클래스가 반드시 있어야 객체를 생성할 수 있다. 다음과 같이 말이다.

public class Person : IPerson
{
    public string Name { get; set; }
    public string Email { get; set; }
}   

C#의 상식으로 보아도 인터페이스(Interface)는 인스턴스를 생성할 수 없으며, 인스턴스를 생성하기 위해 구현 클래스가 반드시 존재햐여 하는 것 쯤은 알고 있을 것이다. 즉, Person 클래스가 있어야 IPerson person = new Person(); 으로 객체를 생성할 수 있다. 분명, 처음 코드의 DynamicProxyObject.InterfaceImplementationType<IPerson>(); 가 뭔가의 마법을 부리는 것이 확실하다고 보면 된다.

#3 - 다이나믹 프록시(Dynamic Proxy) 의 다른 구현 방법들

방금 언급한 DynamicProxyObject.InterfaceImplementationType<IPerson>(); 코드가 바로 class 로 구현조차 되지 않은 클래스를 동적(Dynamic)으로 런타임에 생성한다. 이 동적 클래스를 런타임에 생성하기 가장 쉬운 방법이 있는데, 그것은 .NET Framework에서 제공하는 CodeDomProvider 이다.

간단히 CodeDomProvider를 언급만 하고 넘어가보자. CodeDomProvider는 C#, VB.NET의 객체 표현을 Code Dom으로 표현할 수 있는데, 각각 CSharp, VB.NET Provider를 제공해 주고, 런타임에 컴파일을 한다. 다만, 컴파일 시간이 동적 객체 생성 기법 중에 가장 느리다.

그리고 C# 3.0부터 지원하는 람다 표현식(Lambda Expression)을 이용하는 방법이다. 이 람다 표현식도 내부적으로 CodeDom과 매우 유사하다. 단점이라면, 객체의 이름(Class Name)과 주소(Namespace) 등을 원하는데로 줄 수 없다. LambdaExpression 클래스의 맴버들이 많은데, 이것들로 모든 구현이 가능하다. 간단한 예제 코드를 보자.

Expression<Func<string, string>> expression = (s) => "Hello " + s;               
var func = expression.Compile();               
var ret = func("POWERUMC");   
Console.WriteLine(ret);

이 코드를 실행하면 “Hello POWERUMC” 라는 결과가 나오는 것을 확인할 수 있다.

이렇게 알게 모르게 이미 .NET Framework에서 다이나믹 프록시(Dynamic Proxy) 기법을 쓰는 곳이 의외로 많다. C# 컴파일러는 익명의 표현식들을 내부적으로 컴파일하면서 메서드로 만들어 버리기도 한다. 이렇게 똑똑한 컴파일러 덕분에 람다 표현식이나 LINQ의 성능이 매우 좋다는 것이다.

이 외에 동적메서드(DynamicMethod) 방법이 있는데, 얘는 그냥 넘어가도 무관하다.

#4 - 다이나믹 프록시(Dynamic Proxy) 개체가 생성되는 일련의 순서

동적 개체가 만들어지려면 어떤 순서가 필요할까?

먼저 객체지향 언어라는 점을 생각해볼때, 객체의 주소(Namespace)와 이름(Type Name)이 필요하다. 필자의 프레임워크 코드 중 Umc.Core.Dynamic.Proxy.Builder 네임스페이스의 ModuleBuilderExtension와 TypeBuilderExtension이 그 역할을 한다.

Type을 생성하기 위해서는 생성자 하나 이상이 반드시 필요한데, Object를 상속하는 타입을 만들어야 한다. 아시다시피 public class Person 클래스는 암묵적으로 public class Person : Object 를 상속하게 된다. 그래서 타입을 생성하고 하나의 생성자를 만들고 나면, 부모 객체인 Object의 생성자(Constructor)를 호출을 해주어야 한다. 당연히 부모가 있어야 자식이 있는 것과 같다.

이 때 반드시 주의해야 하는 것이 있다. 생성자는 인스턴스를 생성하는 생성자가 있고, static 생성자가 있다. 만약 static 생성자인 경우는 부모 객체인 Object 생성자(Constructor)를 호출하면 안된다.

Object 생성자를 타입의 생성자 안에서 호출하려면 다음과 같은 코드를 이용하면 된다.

il.Emit(OpCodes.Ldarg_0);  
l.Emit(OpCodes.Call, typeof(object).GetConstructors([0\]);  

이제 IPerson의 프로퍼티(Property)를 구현한다.

IPerson의 프로퍼티 목록은 리플랙션(Reflection)을 이용하여 쉽게 얻어낼 수 있다. 간단히 typeof(IPerson).GetProperties(); 면 된다. (프레임워크 내부에선 더 복잡할 수 있음)

C#의 프로퍼티는 컴파일을 하게 되면 프로퍼티가 메서드로 치환된다. 예를 들어, public string Name { get; set; } 코드는 다음 처럼 컴파일러가 치환한다.

public string get_Name() { ... }  
public void set_Name(string name) { ... }  

그러므로, 다이나믹 프록시를 구현할 때도 프로퍼티를 선언해 준 후에 get, set 메서드를 구현해 주어야 한다. 이를 Umc.Core에서 다음고 같이 구현하였다.

public ICodeLambda Get()
{  
var type       = new TypeBuilderExtension(null, this.TypeLambda.TypeBuilder);  
var methodAttr = this.TypeLambda.MethodAccessor.MethodAttribute | MethodAttributes.NewSlot | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig;  
var method     = type.CreateMethod(methodAttr, propertyBuilder.PropertyType, String.Concat("get_", propertyBuilder.Name), Type.EmptyTypes, null, false);  

propertyBuilder.SetGetMethod(method);  

return new CodeLambda(this.TypeLambda, method.GetILGenerator());
}  

public ICodeLambda Set()
{  
var type = new TypeBuilderExtension(null, this.TypeLambda.TypeBuilder);  

var methodAttr = this.TypeLambda.MethodAccessor.MethodAttribute | MethodAttributes.NewSlot | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig;  
var method     = type.CreateMethod(methodAttr, typeof(void), String.Concat("set_", propertyBuilder.Name), new Type[] { propertyBuilder.PropertyType }, null, false);  
method.DefineParameter(1, ParameterAttributes.HasDefault, "value");  

propertyBuilder.SetSetMethod(method);  

return new CodeLambda(this.TypeLambda, method.GetILGenerator());

}  

그리고 get, set 메서드의 내부 코드는 다음과 같이 구현된다.

public ITypeLambda GetSet()
{  
this.TypeLambda.FieldAccessor.FieldAttribute = FieldAttributes.Private;  
var field = this.TypeLambda.Field(this.propertyBuilder.PropertyType, String.Concat("__", propertyBuilder.Name));  

var get = this.Get();
{
    get.Return(field);
}  

var set = this.Set();
{
    set.IL.Emit(OpCodes.Ldarg_0);
    set.IL.Emit(OpCodes.Ldarg_1);
    set.IL.Emit(OpCodes.Stfld, ( (IValuable<FieldBuilder>)field ).Value);

    set.Return();
}  

return this.TypeLambda;
}  

위의 코드가 그 흔한 프로퍼티의 구현부인 public string Name { get { return this._name; } set { this._name = value; } } 를 구현하는 코드가 되겠다. 별거 아닌 코드지만 내부적으로 프로퍼티와 메서드를 생성하고, 그 구현 코드를 IL 코드로 재구현 하면 역시 조금 난이도가 높아진다.

메모리에서 개체 포인터의 엑세스를 방지하기 완료 방법

지금까지는 메모리에 동적인 코드들을 IL 코드들로 구현하였다. 모든 구현이 완료되었으면 더 이상 메모리에서 IL 코드가 위변조 되거나 .NET 코드 정책에 위배되지 않도록 엑세스 접근을 막아야 한다.

이 코드는 Umc.Core.Dynamic.Proxy.Lambda.TypeLambda 클래스의 CreateType() 메서드가 완료시킨다. 이 코드는 다음과 같이 구현 되었다.

public Type ReleaseType()
{  
if (this.TypeBuilder == null)
{
    throw new NullReferenceException(this.TypeBuilder.GetType().Name);
}  

return this.TypeBuilder.CreateType();
}   

코드 구현의 완료를 알렸다면 이제 동적 타입(Dynamic Type) 개체가 완성이 된다. 즉, 맨 처럼 IPerson 인터페이스를 구현하는 동적 개체(Dynamic Object)인 Person 개체의 타입이 되는 것이다. 즉, 이 Person 개체는 런타임에서 메모리상에 만들어진 것이 되겠다.

이리하여 Activator.CreateInstance(type) 으로 메모리상에 만들어진 Person 클래스를 생성할 수 있게 되는 것이다.

다이나믹 프록시(Dynamic Proxy) 개체의 모습

메모리상에서 만들어진 Person 개체의 타입은 내부적으로 규칙을 정해서 만들었다. Person 객체를 생성하여 이 객체의 타입을 보면 괴상한 이름으로 지어졌다.

var type   = DynamicProxyObject.InterfaceImplementationType<IPerson>();  
var person = (IPerson)Activator.CreateInstance(type);  

// 결과 -> dynamic_46c8ecaab09b41cbaa814511f69f1974

이 타입이 속한 주소(Namespace)와 모듈(Module)을 직접 디스크에 저장할 수도 있는데, 이런 방법을 이용하여 코드 난독화(Code Obfuscation) 처럼 사용할 수도 있다.

또 다이나믹 프록시(Dynamic Proxy)를 활용해 중간에 로깅(Logging)을 하거나 트랜잭션(Transaction) 처리를 할 수 있는 AOP 를 구현할 수도 있는 것이다. (AOP 구현 기법 중 하나이며, 다른 방법이 더 있다.)

또, 사용 편의성을 위해 고안된 것이 체이닝(Chaining) 방법을 이용하여 런타임 코드를 더 쉽게 만드는 방법을 Umc Core 프레임워크에서 제공한다. 다음의 코드를 보면 쉽게 이해가 될 것이다.

using System;  
using System.Linq;  
using System.Reflection;  
using Umc.Core.Dynamic.Proxy.Lambda;  

namespace ConsoleApplication1
{  
internal class Program
{
    private static void Main(string[] args)
    {
        string typeName = Guid.NewGuid().ToString("N");
        string methodName = Guid.NewGuid().ToString("N");

        var assembly = new AssemblyLambda().Assembly();
        {
            var module = assembly.Module();
            {
                var type = module.Public.Class(typeName);
                {
                    var constructor1 = type.Public.Constructor();
                    {
                        constructor1.Emit.EmitWriteLine("This is constructor");
                        constructor1.Return();
                    }

                    var constructor2 = type.Private.Static.Constructor();
                    {
                        constructor2.Emit.EmitWriteLine("This is (private static) constructor");
                        constructor2.Return();
                    }

                    var method = type.Public.Static.Method(methodName);
                    {
                        method.Emit.EmitWriteLine("This is emitted writeline");
                        method.Return();
                    }
                }

                var releaseType = type.ReleaseType();
                var obj = System.Activator.CreateInstance(releaseType);
                var releaseMethod = releaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);

                Console.WriteLine("Release type is {0}", releaseType.AssemblyQualifiedName);
                Console.WriteLine("Release method is {0}", releaseMethod.Name);

                releaseType.GetMethod(methodName).Invoke(null, null);

                Console.WriteLine("".PadLeft(50, '-'));

                releaseType.GetMethods().ToList().ForEach(m => Console.WriteLine("Method Name is " + m.Name));
            }
        }
    }
}
}



// 결과 ->  
This is (private static) constructor  
This is constructor  
Release type is bdfd27e5e7ed446d8702fe172733d937, 4a8f6dd24e1f4054b551cca95ad0870b, Version=0.0.0.0, Culture=neutral,  PublicKeyToken=null  
Release method is 389b4a43a1ba4dc1b1391b5b06633645  
This is emitted writeline  
Method Name is 389b4a43a1ba4dc1b1391b5b06633645  
Method Name is ToString  
Method Name is Equals  
Method Name is GetHashCode  
Method Name is GetType  

위의 코드와 같이 좀 더 직관적으로 동적 코드를 만드는 방법을 Umc Core에서는 제공해 주고 있다.

재미있는 것은 메서드의 이름이다. 결과 출력 중 Method Name is 389b4a43a1ba4dc1b1391b5b06633645 이 있는데, 놀랍게도 메서드의 이름이 숫자로 시작한다. C# 언어 사양 중 변수의 타입, 변수 등의 이름은 반드시 알파벳 문자로 시작해야 함에도 불구하고, 숫자로 시작하다니…

이는 C#의 언어 사양과 CIL(Common Intermediate Language) 언어의 사양이 다르기 때문이다. 심지어 숫자가 아닌 특수문자로 시작해도 된다. 앞서 말했다시피 이런 방법을 이용하여 코드 난독화(Code Obfuscation) 가 가능하다.

비록 대부분의 Umc Core 소스 코드를 공개하지 않았지만, 미공개 코드 중 코드 난독화 툴도 함께 포함이 되어있고, Junk Assembly를 만드는 툴, AOP based Compile 툴 등도 포함이 되어있다. ㅎㅎㅎ; 아쉽지만 여전히 미공개 분은 공개하지 않을 예정이다. 더불어, 관심있는 독자라면 CIL 언어 스팩의 사양을 살펴보는 것도 좋을 것이다.

결론

지금까지 살펴본 다이나믹 프록시(Dynamic Proxy)를 살펴보았다. 이 기법은 IoC, DI, AOP, ORM 등 최신 객체 제어 기술들의 가장 근간이 되는 기법이다. 여러분들이 최신 기술들로 IoC, DI, AOP 등을 구현하고 싶다면 당장 오픈소스를 이용하면 될 것이다.

아마 필자처럼 .NET 기반으로 다이나믹 프록시(Dynamic Proxy)를 직접 구현해서 쓰는 것은 매우 드문 일이다. 이미 검증된 오픈 소스들도 많기 때문에 굳이 이를 구현해서 쓸 필요는 없다.

하지만 이런 오픈 소스를 이용하여 쓰는 것은 그리 어렵지 않겠지만, 이런 오픈 소스를 이용하여 어떻게 코드를 만드느냐는 성능이나 내부적으로 동작하는 효율성에 영향을 미칠 수 있다. 즉, 자신이 오픈 소스를 이용하여 만든 코드가 효율적인 코드, 그리고 메모리상에서 Clean Code로 동작하느냐는 별개의 문제이다.

필자와 같이 다이나믹 프록시(Dynamic Proxy)를 구현해 보았다면 여러분들이 얼마나 위험한 코드를 많이 만드는지 알 수 있다. 즉, 오픈 소스를 써보기만 했다면 절대로 알 수 없는 고급 기술이다.

ASP.NET MVC 등과 같은 프레임워크에 IoC 등이 통합되면서 내부적으로 이와 유사한 방법을 쓴다. 특히 웹 개발에 있어 IoC와 같은 프레임워크는 굉장히 위험할 수 있다. 설명하자면 지금까지 쓴 내용보다 더 복잡하고 많은 내용으로 설명이 필요할지도 모른다.

만약 관리 언어(Managed Language) 등으로 만들어진(자바도 구현 기법이 유사함) 응용 프로그램의 고성능 튜닝, 트러블 슈팅, .NET 플랫폼에 관심이 많은 독자라면 반드시 이해하고 넘어가야 할 내용들이다. 그리고 오픈 소스를 이용하여 다이나믹 프록시(Dynamic Proxy)를 간접적으로 사용만 한다면 가볍게 읽고 넘어가도 좋다. 소소한 개발 일상의 이야깃 거리도 될 것이다.


필자는 이 외에도 다이나믹 프록시(Dynamic Proxy)를 바탕으로 이를 IoC와 통합하고 AOP, ORM 등을 구현하는 방법도 알아볼 것이다. Umc Core는 오픈 소스로 공개되어 있으며 위키 페이지를 만들겸 하나씩 정리하고자 한다. 

'Umc Projects > Umc.Core' 카테고리의 다른 글

Umc Core IoC 통합 컨테이너 #1  (0) 2013.05.24
Umc.Core 프레임워크 다이나믹 프록시(Dynamic Proxy) #1  (0) 2013.05.23
Umc.Core 미공개 Preview  (6) 2008.05.14
Umc.Core 란?  (0) 2007.12.01
Posted by 땡초 POWERUMC

댓글을 달아 주세요

개요

Java 8 버전에서 Lambda 표현을 지원한다. 아직 Java 8은 Beta 버전이다. 여러 언어 중에서 Lambda 표현을 지원하지 않는 언어로 손꼽힌다. Wikipedia에서 Anonymous Function을 참고해보면 Java 언어가 언어의 표현력에 있어서 추세를 따라가지 못하는 것이 아닐까 생각한다.

반면,

  • C#은 2007년도에 C# 3.0 버전에 LINQ 라는 대주제를 중심으로 Lambda, Anonymous Class, Extension Methods를 내놓았고,
  • C# 4.0은 2010년도에 Dynamic이라는 대주제를 중심으로 동적 프로그래밍이 가능해졌다.
  • C# 5.0은 2012년도에 비동기 라는 대주제를 중심으로 비동기 프로그래밍을 언어적으로 지원한다.

Wikipedia에서 C# 역사에 대해 더 자세히 알고 싶은 분은 'C# (programming language)' 를 참고하면 좋겠다.

Java를 이용하여 프로그래밍을 하려고 하면 정말 C#이 많이 생각난다. C#에서 한 줄짜리 문장을 Java에서는 십여 줄 넘는 경우가 많기 때문이다. 굳이 예를 들자면, 우리나라에서 유행하는 줄임말 '엄친아'를 풀어서 '엄마 친구의 아들' 로만 말해야 하는 것과 같은 느낌이랄까… 어쨌든 Java는 Java만의 매력이 있는 법. 그 매력을 찾아보는 것도 재미있겠다.

각설하고, 먼저 Java 8을 사용하여 개발할 수 있는 환경부터 간단히 살펴보자.

현재 Java 8 버전은 베타 버전이다. 현재 Java 8은 Sun사의 JDK를 칭한다. 그러므로 Oracle 사이트에서 Java 8 버전을 다운로드 받을 수 없다.

그리고 Project Lambda를 지원하는 개발 툴을 사용해야 한다. 다음의 링크의 NetBeans와 IntelliJ IDEA 12 버전에서 Project Lambda를 사용해볼 수 있다. 아래의 링크에서 다운로드 받을 수 있다.

설치와 JDK 1.8 버전의 환경 구성이 완료되었으면 Lambda 표현을 Java 에서 사용할 수 있다.

Java 8 의 Lambda 샘플 예제 간단한 예제만 소개하겠다.

(Java에서 권장하는 네이밍이나 코드 구현 방식에 맞지 않는 부분이 있더라도 양해 바란다.) 간단한 더하기 계산을 Lambda 표현으로 작성하면 다음과 같다. 



위의 코드로 말미암아 Lambda 표현은 (arguments) -> { … } 로 표현할 수 있겠다.

간단하게 Thread를 돌리는 코드를 Lambda 표현식으로 작성해보자. 




다음은 ExecutorService를 Lambda 표현으로 작성하였다. 





Java의 Lambda 이야기가 나온 김에 어떻게 Lambda 표현으로 발전하였는지도 짤막하게 보자.

원래 이런 코드가 있었다. Runnable Interface를 구현하는 코드이다. 




또는 Java의 Local Class를 이용할 수 있다. Local Class는 메서드 구현부에서 Class를 선언하여 이를 인스턴스화 할 수 있다.

위의 Runnable Interface를 구현한 코드를 Anonymous Class(익명 클래스)로 표현할 수 있게 되었다. 그래서 아래의 예제와 같이 Interface를 구현하는 Class를 만들지 않아도 된다. 



위 Anonymous Class를 Lambda 표현으로 작성하면 더 간결하게 표현할 수 있다. 


단, Java 8의 Lambda 표현에 제약이 있다.

그리고 Project Lambda를 소개하는 페이지의 Functional Interfaces 에서 제약에 대한 설명이 있다. 하지만 이는 근본적으로 Java에서는 C/C++의 Pointer를 표현할 방법이 없는 이유이다. 그러므로 함수를 가리키는 Pointer도 있을 수 없다. 반면, C#에서는 함수포인터를 표현하기 위해 Delegate(대리자)를 지원한다. C#에서는 함수포인터를 안전하게 다룰 수 있다.

그래서 Java에서는 함수포인터를 표현하기 위해서 Listener 형태의 패턴을 주로 사용한다. 다른 말로 Observer 패턴이라고 부른다. Java의 Thread가 대표적이다. Java의 Thread는 Runnable을 인자로 받는 생성자가 있다. 위의 코드에서도 볼 수 있듯이 Runnable은 void run() 메서드만 달랑 가지고 있는 Interface이다. Java의 Thread는 이 Runnable Interface만 알고 있으면 되고, Runnable Interface를 구현하는 인스턴스를 Thread에게 넘겨주면 된다.

반면, C#의 Thread는 Delegate(대리자-안전한 함수 포인터)를 이용하여 Thread를 실행한다. C# 컴파일러는 Delegate를 결국 Class 로 취급한다. 이로 말미암아 Java와 C#에서 포인터라는 것은 언어적으로는 전혀 메커니즘으로 작동하지만 런타임 입장에서는 유사한 메커니즘으로 동작한다는 것을 알 수 있다. 하지만 Java에서 함수포인터를 흉내를 낼 수 있는 방법은 있다. 키/쌍의 컬렉션을 이용하여 참조를 전달하는 방법이다. 아래는 간단한 예제 코드이다. 



어찌되었든 결국, Java의 Lambda는 Interface를 이용하여 Lambda 함수를 만듦으로써 Interface의 함수가 단 1개만 있어야 Lambda 표현을 할 수 있는 제약이 생겼다. Interface를 이용하여 Lambda를 표현한다고 함은 내부적으로 Proxy 객체를 생성하여 그 안에 Lambda 표현을 메서드로 만든다.

아래의 익명 클래스를 보자. 아래의 runnable 로컬 변수를 리플랙션을 이용하여 getMethods() 의 목록이다. 



아래의 Lambda 표현을 보자. 마찬가지로 runnable 로컬 변수를 리플랙션을 이용하여 getMethods() 의 목록이다. 


이를 통해 Java의 Lambda 표현식은 내부적으로 Proxy 클래스가 생성됨이 확인되었다. 그런데 이 Proxy 클래스가 언제 생성이 될까? 컴파일 타임에 생성이 될까, 아니면 런타임에 생성이 될까?

이를 JD-GUI 도구를 이용하여 Decompile 결과를 확인하려고 하였으나, Java 1.8.0 버전에 대해서 JD-GUI 가 올바르게 인식을 하지 못해 전혀 class 파일을 전혀 읽을 수 없다. 대신 .class 파일을 Text Editor 로 열어서 대략적인 내용을 확인할 수 있는데, Text Editor 에서는 Lambda 표현으로 구현된 Proxy Class 를 찾을 수 없었다. 따라서 Lambda 표현은 런타임에 구현 객체 Proxy 가 생성된다는 것을 알 수 있다. (다만, 확신은 못하겠다.)

한가지, Java 8의 Lambda 표현의 다른 점이라면 Lambda 표현의 Proxy 객체는 java.lang.invoke.MagicLambdaImpl 클래스를 상속한다는 점이다. 앞서 얘기했듯이 JD-GUI 도구가 Java JRE 1.8.0 의 rt.jar 파일을 상위 호환성이 아직 지원되지 않아 구현 내용을 알 수는 없었다. 이는 좀 더 Java 8의 Release 시기가 다가오기를 기다려야 할 것 같다. 



결론은 Java 8의 Lambda 표현을 하기 위해서는 Interface 의 구현 함수는 반드시 1개여야 한다는 점이다.

Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 짜두 2013.01.17 08:59 Address Modify/Delete Reply

    이제 베타버전이니 좀더 발전해야 겠네요. 굉장히 빠른 속도로 발전해나가지 않을까하는 생각이 드네요.ㅎ

[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [1]
[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [2]
 
 
 
지난 아티클에서 우리는 List<> 컬렉션에 특정 요소를 탐색하기 위해 delegate 와, 익명 메서드, 람다식을 이용한 방법을 알아보았다.
 
List<> 컬렉션의 Find와 FindAll 메서드는 List<> 클래스가 제공하는 메서드지만, 이와 비슷한 확장 메서드를 손수 구현해 보도록 할 것이다.
 
 
이번에도 다음의 List<> 컬렉션의 데이터를 가지고 예제를 만들어 볼 것이다.
 
List<int> arr = new List<int>();
arr.Add(1);
arr.Add(2);
arr.Add(3);
arr.Add(4);
 
지난 강좌에서 보듯 다음과 같이 List<> 클래스의 Find 메서드를 이용하여 특정 요소를 탐색한다.
 
int result = arr.Find(o => o == 2);
Console.WriteLine(result);
 
 
 
List<Int>.Find 메서드를 확장 메서드로 구현
 
확장 메서드를 구현하기 위해 MyExtension 이라는 클래스를 만들고 UmcFind 라는 메서드를 만들자.
 
static class MyExtension
{
           public static int UmcFind(this IEnumerable<int> arr, Predicate<int> p)
           {
                     List<int> ret = new List<int>();
                     foreach (var v in arr)
                     {
                                if (p(v))
                                          return v;
                     }
 
                     return 0;
           }
}
 
위 확장 메서드는 다음과 같이 호출이 가능하다.
 
int result = arr.UmcFind(o => o == 2);
Console.WriteLine(result);
 
 
Public static int UmcFind(this IEnumerable<int> arr, Predicate<int> p)
 
의 선언을 보자.
 
인자값에 오는 this 는 확장메서드를 지원할 원본 객체의 타입이다. 즉, 우리는 List<int> 에 대한 객체에만 확장 메서드를 지원하게 된다.
 
Predicate<Int> p
 
는 .NET Framework 에서 선언된 bool 을 리턴하는 델리게이트이다.
 
즉, o => o == 2 람다식의 “ o == 2 “ 의 구현부는 Predicate 델리게이트에게 위임된 것이다.
때문에,
 
if( p( v ) )
 
와 같이 Predicate 의 리턴이 bool 이므로 람다식의 조건을 True/False 로 리턴 받을 수 있다.
 
 
 
List<Int>.FindAll 메서드를 확장 메서드로 구현
 
아래와 같이 사용되는 List<int>.FindAll 메서드를 확장 메서드로 구현해 보자.
 
List<int> result = arr.FindAll(o => o >= 2 && o <= 3);
result.ForEach(o => Console.WriteLine(o));
 
 
이번에는 .NET Framework 가 제공하는 델리게이트를 사용하지 않고, 직접 델리게이트를 선언해 볼 것이다.
 
delegate TResult FindAllHandler<T, TResult>(T t);
 
FindAll 확장 메서드를 구현하기 위해 제네릭 델리게이트를 선언해 보았다.
첫번째 T 는 받을 인자 타입이고, 두번째 TResult 는 리턴 타입이 된다.
 
그럼 UmcFindAll 이라는 확장 메서드를 만들어 보자.
 
static class MyExtension
{
           public static List<int> UmcFindAll(
            this IEnumerable<int> arr, FindAllHandler<int,bool> p)
           {
                     List<int> result = new List<int>();
 
                     foreach (var v in arr)
                     {
                                if( p(v) )
                                          result.Add( v );
                     }
                     return result;
           }
}
 
위의 확장 메서드는 다음 처럼 사용이 가능하다.
 
List<int> list = arr.UmcFindAll(o => o >= 2 && o <= 3);
list.ForEach(o => Console.WriteLine(o));
 
 
 
보시는 바와 같이 List<> 클래스가 제공하는 FindAll 메서드와 동일한 기능을 하는 확장 메서드를 만들어보았다.
 
UmcFindAll 확장 메서드는 제네릭 델리게이트를 선언하여 예제를 만들어 보았지만, UmcFind 확장메서드를 유심히 본 독자라면 보는데 큰 어려움이 없을 것 같다.
 
 
이것으로. 람다식의 간략한 이용 방법과 람다식을 사용하는 확장 메서드에 대해서 알아 보았다.
Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 상준 2011.03.24 14:17 Address Modify/Delete Reply

    글 너무 너무 잘봤습니다.
    하지만 이해하기 너무 힘드네요...
    실례지만 해당 소스코드도 있었으면..
    더좋은 글이 되지 안을까 생각합니다..

[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [1]
[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [2]



C# 3.0 에서 LINQ 를 위해 많은 언어적 개념이 도입되었다.
확장 메서드(Extension Methods)와 Lambda Expression 등이 바로 그것이다.
그중 Lambda Expression(이하 람다식) 은 Ruby 에서 먼저 나왔다고 하지만, C# 3.0 에서의 람다식은 LINQ 와 확장 메서드와 굉장히 사슬처럼 엮여 있는 듯한 모습이다.
 
람다식은 대리자(Delegate)와 제네릭 메서드의 복잡한 식을 간결하게 줄여줄 수 있다.
 
=>
 
연산자를 이용하여, 익명 메서드(Anonymous Method) 의 여러줄의 코드를 단 한줄의 코드로 줄여줄 수 가 있다.
 
좀더 자세한 설명히 필요하다면 다음을 참고 하도록 하자.
http://msdn.microsoft.com/msdnmag/issues/07/09/BasicInstincts/Default.aspx?loc=ko
 
 
우리가 사용할 예제는 다음의 List<int> 컬렉션을 사용할 것이다.
 

List<int> arr = new List<int>();
arr.Add(1);
arr.Add(2);
arr.Add(3);
arr.Add(4);

 
 
List<> 컬렉션은 Find 라는 메서드를 제공한다. ( 확장 메서드가 아님 )
이 메서드는 컬렉션의 특정 요소를 찾기 위해 사용되어 지는데, C# 이 제공하는 Predicate<> 델리게이트를 통해 True/False 를 반환하는 델리게이트이다.
 
그럼 List<> 컬렉션의 특정 요소를 찾기 위해 다음과 같이 사용된다.
 

static void Main(string[] args)
{
           List<int> arr = new List<int>();
           arr.Add(1);
           arr.Add(2);
           arr.Add(3);
           arr.Add(4);
 
           Predicate<int> p = new Predicate<int>(Compare);
           int result = arr.Find(p);
           Console.WriteLine(result);
}
static bool Compare(int i)
{
           return i == 2;
}

 
 
Predicate<int> p = new Predicate<int>(Compare);
 
Predicate<> 대리자는 Compare 메서드에게 일거리를 할당한 것을 볼 수 있다.
 
그럼, 가상 메서드를 이용하여 코드양을 줄여 보도록 하자.
 

int result = arr.Find(new Predicate<int>(
                                delegate(int i)
                                {
                                          return i == 2;
                                }
                     ));
Console.WriteLine(result);

 
 
위와 같이 당연히 결과는 “2” 가 나타났다.
델리게이트를 통해 i==2 라는 요소를 찾기위해 int 인자와 bool 을 리턴하는 메서드를 만들어야 하지만, 위의 delegate 키워드의 구문은 가상 메서드를 만들게 됨으로써 코드양이 많이 줄어 들게 되었다.
 
하지만, 람다식을 이용하여 더 줄여보도록 하자.
 

Console.WriteLine(arr.Find(o => o == 2));

 
 
위 7개 라인을 단 한줄로 해결하였다.

 
사실 이 구문이 선듯 이해가 가지 않는 필자는 무진장 머리를 굴려보았다.
잘 이해를 하기 위해 람다식을 마치 메서드가 있는 구문이라고 머리속으로 상상해보기 바란다.
 
 
 
=> 연산자를 기준으로 왼쪽은 넘길 인자값이 되는 것이고, 오른쪽은 내용이 되는 것이다.
 
그래도 잘 이해가 안된다면 마치 메서드가 존재한다는 상상 훈련을 해보면 그리 오래 걸리지 않고 쉽게 이해가 될 것 같다.
다음 2편에서는 제네릭 대리자를 통해 람다식이 가능한 확장 메서드를 만들어 보도록 하자
Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 지송 2010.06.17 12:57 Address Modify/Delete Reply

    람다식 찾다왔는데 역시 이해가 쉽게 써놓으셨네요.

    좋은 하루 되세요 !