adSense 900*70


블로그 이전

https://ryukato.github.io/로 블로그를 이전합니다. 

글을 마크다운으로 작성해서 html로 변경하여 올리는 작업도 너무 번거롭고해서, 작성하고 올리는 게 좀 더 수월하여 옮기게 되었습니다. 

현재 이전하기 위해 글들을 옮기고 있네요. 다 옮기더라도 현재의 블로그에는 새로운 글이 추가는 않되겠지만 유지만 하려고 합니다.


Angular2 Testing (working) JavaScript

Angular2 Testing

Why do test?


  • Regression test: 기존의 코드들이 정상 작동하는 것을 보장하기 위한 것이다. 이 것을 잘 준수할 경우, 코드의 Refactoring을 지속적으로 수행할 수 있다.
  • 특정 기능의 작동을 확인하고 정상적인 조건이 아닌 경우에 대한 결과를 확인하여 조치할 수 있다.
  • 설계 혹은 구현상의 실수(결함)을 확인하여, 조치하도록 한다. 테스트 코드를 작성하기 힘들거나, 수행하기 어려운 경우, 대부분 설계상의 결함이 있는 경우가 많다.
  • 결함을 늦게 발견할 수록 고치기 힘들어 진다. 즉, 비용이 많이 발생하게 되므로 결함은 되도록 빨리 발견하는 것이 좋으며, 테스트는 이를 가능하게 해준다.

With What?


  • Jasmine: 기본적인 테스트 수행에 필요한 대부분의 것들을 제공하며, HTML test runner를 제공하여 browser에서도 테스트를 수행할 수 있다.
  • Angular testing utilities: 테스트를 수행하기 위한 환경을 생성하여 준다. Angular가 실행될 수 있는 환경하에서 특정 기능이 수행될 수 있는 조건과 제어를 할 수 있도록 해준다.
  • Karma: Karma test runner는 application 개발 동안 단위 테스트를 작성할 수 있도록 도와준다. 단위 테스트를 작성하는 것은 프로젝트의 개발과 지속적인 통합(CI)과정에서 핵심적인 부분입니다.
  • Protractor: 사용자 경험과도 같은 Ent-to-end(e2e)테스트를 작성하고 실행하기 위해 사용됩니다. 실제 application이 실행되도록 프로세스가 실행되며, 또 다른 프로세스는 protractor 테스트들을 실행합니다. protractor 테스트들을 실행은 browser상에서 실행되는 application의 반응을 검증합니다.

How?

Setup Project


  1. 프로젝트 생성: ng new angular2-testing
  2. 프로젝트 환경 확인



    • 1st.spec.ts라는 파일 생성 후, 아래의 코드 작성


        describe('1st tests', ()=> {
      it('true is true', () => expect(true).toBe(true));
      });
    • 테스트 실행: 아래의 명령어를 실행하게 되면, application code들과 테스트 code들을 compile하고 karma를 실행하여 테스트를 수행한다. 이때 실행한 프로세스는 계속 실행 중이며, 파일의 변경을 감지하여 변경이 될때마다 compile 및 테스트를 수행한다.


      ng test
      (*위의 명령어는 package.json의 scripts에 선언되어 있다.)



  3. 테스트 실패 결과 확인
    1st.spec.ts 파일의 내용을 아래와 같이 변경 한다. 그럼 watcher가 변경을 감지하고 compile 및 테스트를 재 실행한다. (1 FAILED)와 같은 결과가 표시되는 것을 확인할 수 있다.


     describe('1st tests', ()=> {
    it('true is true', () => expect(true).toBe(true));
    });
  4. 디버깅 테스트

    • karma window를 선택한다.
    • DEBUG버튼을 선택하게 되면, 새로운 탭이 열린다. 이때 윈도우는 ctrl + shift + I, 맥은 command + option + I를 눌러 개발자 툴을 실행한다.
    • Sourcex탭으로 이동하여, control 혹은 command + P를 눌러 1st.spec.ts파일을 검색하여 찾는다.
    • it함수를 호출하는 곳에 break point를 걸고, 새로고침 하면 break point가 걸리는 것을 확인할 수 있다.

Component 테스트

대상 Component 작성

src/app/banner-inline.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'app-banner',
template: '<h1>{{title}}</h1>'
})
export class BannerComponent {
title = 'Test Tour of Heroes';
}
src/app/banner-inline.component.spec.ts

ngOnInit 메서드는 component life cycle상의 init과 같은 이벤트를 처리하는 메서드로 Angular가 component 생성을 완료하였을때 호출되는 메서드 이다. 그런데 twain.service는 Promise를 반환하고 있고, 그 반환된 Promise를 twain.component에서 받아 처리하고 있다.
즉, twain.component의 생성이 완료된 다음에 Promise를 resolve하여 처리한다는 것이다.
그래서 …..

/* tslint:disable:no-unused-variable */
/*
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { BannerInlineComponent } from './banner-inline.component';

describe('BannerInlineComponent', () => {
let component: BannerInlineComponent;
let fixture: ComponentFixture<BannerInlineComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BannerInlineComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(BannerInlineComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

*/

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { BannerInlineComponent } from './banner-inline.component';

describe('BannerComponent (inline template)', () => {
let comp: BannerInlineComponent;
let fixture: ComponentFixture<BannerInlineComponent>;
let de: DebugElement;
let el: HTMLElement;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [BannerInlineComponent], // declare the test component
});

fixture = TestBed.createComponent(BannerInlineComponent);

comp = fixture.componentInstance; // BannerComponent test instance

//query for the title <h1> by CSS element selector
de = fixture.debugElement.query(By.css('h1'));
el = de.nativeElement;
});

it('should display original title', () => {
fixture.detectChanges();
expect(el.textContent).toContain(comp.title);
});

it('should display a different test title', () => {
comp.title = 'Test Title';
fixture.detectChanges();
expect(el.textContent).toContain('Test Title');
});
});

테스트 내용


  • BannerInlineComponent 객체가 가진 title속성이 정상적으로 \태그 안에 들어갔는지를 검증한다.

테스트 코드 설명

TestBed

  • Angular testing moudle(@NgModule)을 생성한다.
  • configureTestingModule 함수 호출을 통해 test 대상 class를 위한 testing module 환경을 설정한다.
  • 실제로는 실 환경의 application module에서 해당 component를 떼어내고, test를 위한 module로 다시 붙여 넣게 된다.
  • configureTestingModule에 인자로 주는 객체는 @NgModule로 meta-object라고 생각하면 된다.
  • 해당 meta-object는 다른 보통의 Angular module이 가질 수 있는 대부분의 속성을 가질 수 있다.
  • meta-object는 테스트 대상이 되는 BannerInlineComponent를 선언한다.
  • 테스트를 수행하기 위해 다른 module이 필요한 경우 imports를 eta-object에 추가하면 된다.

createComponent (* synchronous)


  • createComponent 호출 이후에는 TestBed를 재 설정할 수 없다. 하면 안된다.
  • TestBed.createComponent를 호출하여, 테스트 대상이되는 component를 생성한다.
  • TestBed.createComponent가 반환하는 객체는 ComponentFixture 타입의 객체로 일종의 테스트 대상 component의 wrapper로 생각하면된다. fixture객체는 component instance 및 debugElement를 가지고 있다.
  • ComponentFixture객체가 가지고 있는 debugElement를 통해 component의 DOM element를 가지고 테스트를 수행할 수 있다.

ComponentFixture

Debugging과 testing을 지원하기 위한 객체이며, 아래의 API문서를 통해 상세 내용을 확인할 수 있다.

detectChanges

해당 메서드를 호출하여 data bidning과 DOM element에 data를 전달하기 위해 호출한다. 해당 메서드를 호출하지 않으면, 테스트 케이스 내에서 data binding은 이루어지지 않는다.
TestBed.createComponent의 호출은 data binding을 실행하지 않으며 change detection을 수행하지 않는다.

아래의 테스트 케이스를 추가하여 해당 내용을 확인할 수 있다.

it('no title in the DOM until manually call `detectChanges`', () => {
expect(el.textContent).toEqual('');
});
automatic detectChanges

명시적으로 detectChanges를 호출하여 검증하는 방법을 선호하지 않는다면, 아래의 코드를 추가하여 data binding이 테스트를 수행하는 동안 자동으로 이루어 지도록 할 수 있다.

import { ComponentFixtureAutoDetect } from '@angular/core/testing';
...

//아래의 구문은 beforeEach에 넣는다.
providers: [{
provide: ComponentFixtureAutoDetect, useValue: true
}]

위의 구문 추가후에 추가적으로 수정을 해줘야 하는 테스트 케이스가 있다.
마지막으로 추가한 테스트 케이스이며 이를 수정하여 테스트 케이스가 성공하도록 수정해보자.

그리고 ComponentFixtureAutoDetect를 사용한다고 해도, component객체의 초기 속성값이 binding된 후에, 변경된 속성값을 다시 binding하기 위해선 detectChanges를 반드시 호출해줘야 한다. 그렇지 않으면 변경되기 전의 값이 계속 DOM element에 binding되어 있다. 아래의 테스트 케이스를 참고하면 된다.

it('should still see original title after comp.title change', () => {
const oldTitle = comp.title;
comp.title = 'Test Title';
// Displayed title is old because Angular didn't hear the change :(
expect(el.textContent).toContain(oldTitle);
});

query method


  • DebugElement class API
  • predicate 함수를 인자로 받는다. (* predicate는 주어진 인자를 비교하여 boolean값을 반환하는 함수)

By

대상 DOM element를 query하기 위한 predicate함수를 반환하기 API 를 제공한다.

외부 template을 사용하는 Component 테스트

TestBed.createComponent는 동기적(synchronous)으로 실행되는 반면, Angular template compiler는 외부 template(templateUrl로 선언된 template)을 읽는 작업을 비동기적(asynchronous)으로 수행한다. 따라서 이전의 테스트는 외부 템플릿을 사용하는 component를 테스트하기에는 부적절하다.

따라서 Angular template compiler에게 파일을 읽을 시간을 주어야 하기 때문에 beforeEach에서 실행되는 부분은 async함수로 한번더 감싸야 한다. 또한 TestBed.configureTestingModule 호출 후에, 명시적으로 component를 compile하라고 알려줘야 하기 때문에 아래와 같이 beforeEach 코드를 변경한다.

import { async } from '@angular/core/testing';
beforeEach(async(
() => {
TestBed.configureTestingModule({
declarations: [BannerInlineComponent], // declare the test component
providers: [{
provide: ComponentFixtureAutoDetect, useValue: true
}]
});

TestBed.compileComponents(); // compile template and css;
fixture = TestBed.createComponent(BannerInlineComponent);

comp = fixture.componentInstance; // BannerComponent test instance

//query for the title <h1> by CSS element selector
de = fixture.debugElement.query(By.css('h1'));
el = de.nativeElement;
}
));

compileComponents

TestBed.compileComponents메서드는 비동기적으로 테스트 module내의 component들을 compile한다.
createComponent메서드와 마찮가지로 TestBed.compileComponents메서드 호출 후에 TestBed를 추가로 설정할 수 없다. 또한 TestBed.compileComponents메서드를 createComponent 호출 전, 마지막 단계로 호출하는 것이 좋다.

그리고 TestBed.compileComponentspromise를 반환하기때문에 아래와 같이 처리할 수 도 있다.

TestBed.compileComponents()
.then(() => {
fixture = TestBed.createComponent(BannerInlineComponent);

comp = fixture.componentInstance; // BannerComponent test instance

//query for the title <h1> by CSS element selector
de = fixture.debugElement.query(By.css('h1'));
el = de.nativeElement;
});

의존관계를 가지는 Component 테스트

일반적으로 Component는 특정 service객체에 대한 의존성을 가지게 된다. 이런 경우 어떻게 테스트를 하는지에 대해 알아보자.
아래의 코드에서 보듯이, WelcomeComponentUserSerivce에 대한 의존관계가 있고 UserSerivce를 통해 현재 로그인한 사용자에 대한 정보를 가져온다.

아래의 명령어를 통해 WelcomeComponent 및 UserService를 추가한다.

ng g component Welcome
ng g service welcome/model/User
src/app/welcome.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from './model/user.service'
@Component({
selector: 'app-welcome',
template: '<h3 class="welcome"><i>{{welcome}}</i></h3>',
styleUrls: ['./welcome.component.css']
})
export class WelcomeComponent implements OnInit {
welcome = '-- not initailized yet --';

constructor(private userService: UserService) {

}

ngOnInit() {
this.welcome = this.userService.isLoggedIn() ? 'Welcome, ' + this.userService.userName() : 'Please log in.';
}

}

아래는 WelcomeComponent를 테스트하는 전체 코드이다.
아래의 테스트 코드 중 UserService를 injection하는 코드는 아래와 같다.

userService = TestBed.get(UserService);

혹은

userService = fixture.debugElement.injector.get(UserService);

위의 방법은 test의 root injector를 통해 injection한다. 단 component가 provider를 override하는 경우에는 작동하지 않는다. component-override 참조

src/app/welcome.component.spec.ts
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { WelcomeComponent } from './welcome.component';
import { UserService } from './model/user.service';

describe('WelcomeComponent', () => {
let component: WelcomeComponent;
let fixture: ComponentFixture<WelcomeComponent>;
let de: DebugElement;
let el: HTMLElement;
let userServiceStub;
let userService;
beforeEach(async(() => {
userServiceStub = {
isLoggedIn: function(){
return true;
},
userName: function(){
return 'Test User';
}
}

TestBed.configureTestingModule({
declarations: [ WelcomeComponent ],
// providers: [ UserService ] // No, Do NOT provide real UserService
providers: [ { provide: UserService, useValue: userServiceStub } ]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(WelcomeComponent);
component = fixture.componentInstance; // BannerComponent test instance
userService = TestBed.get(UserService);

de = fixture.debugElement.query(By.css('.welcome'));
el = de.nativeElement;
});
;
}));

it('should create', () => {
expect(component).toBeTruthy();
});

it('should welcome the user', () => {
fixture.detectChanges();
const content = el.textContent;
expect(content).toContain('Welcome', '"Welcome ..."');
expect(content).toContain('Test User', 'expected name');
});

it('should welcome "Test User"', () => {
userService.userName = function(){
return 'Bubba';
} // welcome message hasn't been shown yet
fixture.detectChanges();
expect(el.textContent).toContain('Bubba');
});

it('should request login if not logged in', () => {
userService.isLoggedIn = function(){
return false;
}; // welcome message hasn't been shown yet
fixture.detectChanges();
const content = el.textContent;
expect(content).not.toContain('Welcome', 'not welcomed');
expect(content).toMatch(/log in/i, '"log in"');
});
});

Async service 테스트 하기

대부분의 Service들은 HTTP 요청을 보내고, 응답을 받아 처리하는 등의 비동기(async)한 처리를 많이 하게 된다. 이런 Service에 의존 관계를 가지는 Component가 있을 경우, 해당 Service에 대한 의존 관계를 테스트시에 잘 처리를 해줘야 Component를 격리된(Isolated)상태에서 테스트 할 수 있게 된다.
아래의 코드를 통해 비동기적인 작업을 처리하는 서비스에 의존 관계가 있는 Component를 테스트하는 방법을 살펴 보자

src/app/shared/twain.component.ts
import { Component, OnInit } from '@angular/core';
import { TwainService } from './twain.service';

@Component({
selector: 'twain-quote',
templateUrl: './twain.component.html',
styleUrls: ['./twain.component.css']
})
export class TwainComponent implements OnInit {
intervalId: number;
quote = '...';

constructor(private twainService: TwainService) {
console.log('twain.component constructor is called.')
}

ngOnInit() {
console.log('twain.component onInit!!! before getQuote');
this.twainService.getQuote()
.then((quote) => {
console.log('twain.component resolve quote!!');
this.quote = quote;
});
console.log('twain.component onInit!!! after getQuote');
}

}
src/app/shared/twain.component.spec.ts

아래의 코드에서 유심히 봐야 할 것은 spy와 spyOn이다. spy와 spyOn을 통해 twain.component에 주입된 twainService의 특정 메서드가 호출을 감시하고 있다가, 해당 메서드가 호출되면 우리가 원하는 값을 반환하도록 할 수 있다. Java에서의 Mockito를 사용한 경험이 있다면 이와 비슷하다.

/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { HttpModule } from '@angular/http';

import { TwainComponent } from './twain.component';
import { TwainService } from './twain.service';

describe('TwainComponent', () => {
let component: TwainComponent;
let fixture: ComponentFixture<TwainComponent>;
let de: DebugElement;
let el: any;
let spy: any;
let twainService: TwainService;
let testQuote = 'test quote';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ HttpModule ],
declarations: [ TwainComponent ],
providers: [ TwainService ]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(TwainComponent);
component = fixture.componentInstance;
twainService = fixture.debugElement.injector.get(TwainService);
spy = spyOn(twainService, 'getQuote')
.and
.callFake(() => {
console.log('spy twain.service getQuote is called!');
return Promise.resolve(testQuote);
})

de = fixture.debugElement.query(By.css('.twain'));
el = de.nativeElement;
});
}));

// synchrnous test
it('should create', () => {
expect(component).toBeTruthy();
});

// synchrnous test
it('should not show quote before OnInit', () => {
expect(el.textContent).toBe('', 'nothing displayed');
expect(spy.calls.any()).toBe(false, 'getQuote not yet called');
});

// synchrnous test
it('should still not show quote after component initialized', () => {
fixture.detectChanges();
// getQuote service is async => still has not returned with quote
expect(el.textContent).toBe('...', 'no quote yet');
expect(spy.calls.any()).toBe(true, 'getQuote called');
});
});

synchronous test

// synchrnous test 주석으로 표시된 it 메서드들은 모두 동기적으로 twain.component를 테스트하는 테스트 케이스들이다. 이들 테스트 케이스을 가지고는 component에 quote가 표시되는지를 테스트 할 수 없다. 그 이유는 twain.service혹은 twain.service를 spy하는 spy객체로부터 quote가 아직 반환되지 않았기 때문이다. npm run test혹은 ng test를 통해 테스트를 수행하면 아래와 같은 로그가 표시되는 것을 확인할 수 있다.

표시된 로그를 유심히 살펴 보면 ngOnInit 메서드 후에 service로 반환된 promise가 resolve되어 quote를 반환한 것을 확인 할 수 있다.
즉, quote가 정상적으로 표시되는 것을 확인하기 위해선 테스트 케이스는 반드시 비동기(asynchronous)해야 한다.

LOG: 'twain.component constructor is called.'
LOG: 'twain.component onInit!!! before getQuote'
LOG: 'spy twain.service getQuote is called!'
LOG: 'twain.component onInit!!! after getQuote'
LOG: 'twain.component resolve quote!!'

asynchronous test

아래의 두 테스트 케이스 메서드를 twain.component.spec.ts에 추가한다. 그리고 it 함수에 전달되는 두번째 인자가 async로 되어 있다.
async는 Angular testing utilities에서 제공하는 함수로 해당 테스트 케이스를 async test zone에서 실행해주어, 비동기적으로 만들어 준다.

아래 두개의 테스트케이스들은 동일한 내용을 확인한다. 두개의 테스트 케이스 모두 getQuote가 반환하는 promise가 resolve되는 것을 기다린 후 quote 내용이 표시되는 것을 확인해야한다.

whenStable

첫번째 테스트 케이스는 whenStable메서드를 호출하고 있다. whenStable는 또 다른 promise를 반환하는데, 반환된 promise는 해당 테스트 케이스내에서 보류(pending)되어 있는 비동기 활동들이 완료(resolve)될때 resolve 된다. whenStable가 반환한 promise내에서 quote를 확인하면 된다.

// asynchronous test
it('shoud show quote after getQuote promise (async)', async(() => {
fixture.detectChanges();

fixture.whenStable()
.then(() => { // wait for async getQuote
fixture.detectChanges(); // update view the quote
expect(el.textContent).toBe(testQuote);
});
}));
fakeAsync

fakeAsync를 사용하면 whenStable과는 달리, fakeAsync test zone에서 실행되는 synchronous한 코드 스타일로 작성할 수 있다.
whenStable을 대체하기 위해선 tick함수를 호출하면 된다. tick함수는 fakeAsync내에서 사용해야 한다. tick함수는 보류(pending)되어 있는 비동기 활동들이 끝날때까지 지연시키는 역활을 한다.
코드 흐름을 파악하기 쉬운 장점이 있지만, fakeAsync내에서는 XHR을 호출할 수는 없다.

  // asynchronous test
it('shoud show quote after getQuote promise (fakeAsync)', fakeAsync(() => {
fixture.detectChanges();
tick(); // wait for async getQuote
fixture.detectChanges();
expect(el.textContent).toBe(testQuote);
}));

jasmine done

만약 jasmine에 익숙하다면, 아래의 코드와 같이 jasmine이 제공하는 done으로도 비동기 테스트를 할 수 있는 것을 알 수 있을 것이다.
그리고 intervalTimer을 포함하는 코드를 테스트 하는 경우, async, fakeAsync를 사용할 수 없는데, 이럴때 done을 사용하여 처리하면 된다.

it('should show quote after getQuote promise (done)', done => {
fixture.detectChanges();

// get the spy promise and wait for it to resolve
spy.calls.mostRecent().returnValue.then(() => {
fixture.detectChanges(); // update view the quote
expect(el.textContent).toBe(testQuote);

done();
});
src/app/shared/twain.service.ts

아래의 서비스 코드는 http로 요청을 보내고 응답을 Promise로 변환하는 예제 코드이다. 요청 주소를 보면 알겠지만 예제 용 코드이다.

import { Injectable } from '@angular/core';
import { Http} from '@angular/http';
import 'rxjs/add/operator/toPromise';
@Injectable()
export class TwainService {

constructor(private http: Http) { }

getQuote(): Promise<string> {
console.log('twain.service getQuote is called');
return this.http.get('http://test.quote.com/twain')
.toPromise().then((response) => response.json()[1]);
}
}
src/app/shared/twain.service.spec.ts

아래의 코드는 twain.service를 테스트 하는 코드이다. Http를 twain.service에 inject해야 하기 때문에, TestBed module설정 시(configureTestingModule)에서 imports에 HttpModule를 설정하였다. 이렇게 하면 Http에 대한 provider가 설정된다. HttpModule을 imports에 선언하지 않으면 No Provider for Http라는 에러를 보게 될 것이다.

/* tslint:disable:no-unused-variable */

import { TestBed, async, inject } from '@angular/core/testing';
import { TwainService } from './twain.service';
import { HttpModule } from '@angular/http';

describe('TwainService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [TwainService]
});
});

it('should ...', inject([TwainService], (service: TwainService) => {
expect(service).toBeTruthy();
}));
});

Isolated Unit test

Angular Testing


Redux introduction JavaScript

Redux

Redux는 객체의 상태를 보관,관리하는 container역활을 한다.
client, server 그리고 native환경 모두 지원하며 테스트 쉽게 할 수 있도록 해준다. 또한 time traveling debugger를 제공하여 실시간 적인 debugging을 할 수 있도록 해준다.

설치

사전 준비사항

NodeJs 설치

Redux 설치

안전된 버전을 설치하기 위해 다음의 명령어를 실행한다.

npm install --save redux

import(사용준비)

Redux를 대부분 CommonJs module로서 Webpack, Browswerify 혹은 Node환경에서 import해서 사용할 수 있다.

module bundler등을 사용하지 않을 경우, node_modules/redux/dist/redux.min.js를 참조하여 사용할 수 있다.

그리고 대부분 다음의 package들을 함께 사용하는 경우가 많다. Redux는 React와 많이 사용되고 있다. 그래서 react-redux를 설치하는 것이고, react로 개발하지 않는다면 설치할 필요는 없을 것으로 보인다.

추가 Package설치

npm install --save react-redux
npm install --save-dev redux-devtools

핵심 사항

application의 상태는 store객체내의 object tree내에 저장이 된다.그리고 state tree의 변경 사항(change)를 저장하는 방법은 action을 통해 이루어져야 한다.

action을 통해 변경 사항을 만들기 위해선, reducer들을 작성하기만 하면된다.

위의 내용을 한번 더 요약하면 아래와 같다.


  • Redux는 단 하나의 불변 객체인 State Tree로 상태 변경을 관리한다.
  • Data의 흐름은 단 방향으로 이루어진다. Data의 흐름은 아래와 같다.

    • Action —> Reducers —> Store —(subscribe)—> View —(dispatch)—> Action

  • 변경 사항을 적용하기 위해선 reducer라는 pure function을 사용한다.

Reducer example

reducer는 아래의 예제에 나오는 것처럼, state, action => state의 signature로 선언되어야 한다. 반환되는 state는 action을 통해 변경된 state를 의미한다. 인자로 선언된 state는 다음과 같은 타입의 변수를 가질 수 있다.


  • Primitive (char, number…)
  • Array
  • Object
  • Immutable.js data structure (List, Stack, Map, OrderedMap, Set….)

반드시 기억해야 할 것은 인자로 넘기는 state 객체가 가변 객체이면 안된다는 것이다.

Redux store가 제공하는 API는 아래와 같다.


  • subscribe : state의 변경 사항을 응답으로 받아 처리한다.
  • dispatch : action을 통해 변경을 발생하도록 한다.
  • getState : 저장되어있는 state를 받아 온다.
import { createStore } from 'redux'

function couter(state = 0, action) {
switch(action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state;
}
}

//create redux store
let store = createStore(counter)

store.subscribe(() => console.log(store.getState()))

//dispatch action(simple object) to change(mutate) the internal state
store.dispatch({type: 'INCREMENT'}) // count => 1
store.dispatch({type: 'INCREMENT'}) // count => 2
store.dispatch({type: 'DECREMENT'}) // count => 1

위의 예제와 같이 actiond인 단순 객체(plain object)를 dispatch하여 상태를 변경한다.

Redux는 여러개의 store를 가지지 않고, 오로지 하나만의 store를 가진다. 대신, root reducer를 여러개의 reducer로 쪼개어 사용이 가능하다.

Simple Example

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Redux basic example</title>
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
</head>
<body>
<div class="">
<p>
Clicked: <span id="value">0</span> times
<button type="button" id="increment">+</button>
<button type="button" id="decrement">-</button>
<button type="button" id="incrementIfOdd">increment if odd</button>
<button type="button" id="incrementAsync">increment async</button>
</p>
</div>
<script>
function counter(state, action) {
if(typeof state === 'undefined') {
return 0;
}
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}

var store = Redux.createStore(counter);
var valueE1 = document.getElementById('value')

function render() {
valueE1.innerHTML = store.getState().toString();
}
render();
store.subscribe(render);

document.getElementById('increment').addEventListener(
'click',
function() {
store.dispatch({type: 'INCREMENT'});
}
);
document.getElementById('decrement').addEventListener(
'click',
function() {
store.dispatch({type: 'DECREMENT'});
}
);

document.getElementById('incrementIfOdd').addEventListener(
'click',
function() {
if(store.getState() % 2 !== 0) {
store.dispatch({type: 'INCREMENT'});
}
}
);

document.getElementById('incrementAsync').addEventListener(
'click',
function(){
setTimeout(function() {
store.dispatch({type: 'INCREMENT'});
}, 1000);
}
)
</script>
</body>
</html>

Install Docker on Mac with Homebrew Docker

Install Docker on Mac with Homebrew

virtualbox 설치하기

brew cask install virtualbox

Docker 설치하기

brew install docker docker-compose docker-machine

Virtual Machine 생성하기

docker image로부터 생성한 container를 실행하기 위한 Virtual Machine을 생성하는 과정이다.

VM 생성

dev라는 이름의 Virtual Machine을 생성한다. 생성 후 해당 vm은 자동 실행된다.

docker-machine create -d virtualbox dev

VM 실행 확인

docker-machine status dev
output
running

VM 중지

VM 중지하고, 다시 시작할 경우, docker-cli를 위해 필요한 환경변수 가져오기의 script를 다시 실행해야 docker run [container-name]을 실행할때 에러없이 실행할 수 있다.

docker-machine stop dev

docker-cli를 위해 필요한 환경변수 가져오기

eval "$(docker-machine env dev)"

Docker container 실행

docker run hello-world
output

아래와 같은 메세지를 보면 성공

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://cloud.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/engine/userguide/

Spring integration JAVA - Tutorial번역

Spring integration

Reference

  • 메세지 기반의 Architectures를 지원한다.
  • 메세지 routing 및 transformation을 지원한다.
  • EIP를 지원한다.

다음과 같은 목표를 지향한다.

  • 복잡한 enterprise 통합 솔루션을 구현하기 위한 간단한 모델
  • Spring 기반의 Application내에서의 메세지 기반의 비동적 행위 구현을 수월하게 한다.
  • 기존 Spring 사용자들을 위한 직관적이고 점진적인 적용.

다음과 같은 원칙을 가진다.

  • 각 Component들은 모듈성과 테스트 용이성을 위해 약한 의존성(Loosely coupled)을 가져야 한다.
  • 비지니스 로직과 통합 로직간 관심사의 분리(separation of concerns)을 강제해야 한다.
  • 확장이 필요한 곳은 되도록 추상화를 해야 하며, 이때 이식성과 재사용성에 초점을 맞추어 설계되어야 한다.

Main Components

Spring 기반의 application은 수직적 관점에서의 계층적 구조와 계층간 interface 기반의 접점 통신 구조를 가진다. 이는 계층간 의존성을 줄이기 위한 것이다. 이 기반에 Message-driven 구조는 수평적 관점을 더하며, Pipes-and-Filters모델로 추상화 할 수 있다.


  • Filter: Message를 생산 혹은 소비하는 객체.
  • Pipe: Filter사이에서 Message를 전달하는 통로 역활을 수행하는 객체.

Message

Message


  1. 모든 타입의 Java객체를 포함할 수 있고, 거기에 더해서 framework이 해당 객체를 다루기 위한 metadata를 포함한다.
  2. 아래와 같은 구성요소가 있다.

    • Headers:

      * key-value 형태의 구조
      * 파일 전달 시 파일명 혹은 메일 사용시 수신자, 발신자, 참조, 제목등과 같은 공통 정보를 포함한다.
      * 전달 매체간 특정 값을 전달하는 용도로도 사용.
    • Payload: 모든 타입의 객체를 포함할 수 있다.

Message Channel

Message Channel

역활 및 특징


  1. Pipes-and-Filters에서 pipe역활을 수행한다.
  2. 생산자(Producer)는 Message를 channel로 전송
  3. 소비자는(Consumer)는 channel로부터 Message를 수신
  4. Messaging component간 의존성을 약하게 만드는 역활을 하는 객체.
  5. P2P(Point to Point) 혹은 Pub-sub(publish/subscribe)구조로 구성할 수 있음.
  6. Pollable Channels은 Queue내에 message를 buffering할 수 있다.

Message Endpoint

역활 및 특징


  1. Pipes-and-Filters에서 filter역활을 수행한다.
  2. 비지니스 로직을 담당하는 application 코드와 messaging framework간의 연결(connect)을 책임진다.. (* 단 application 코드를 침해하진 않는다.)
  3. 비지니스 로직을 담당하는 application 객체는 Message, Message channel의 존재를 알 필요가 없다.
  4. Message channel과 mapping 된다.

Transformer

역활 및 특징


  • Message의 내용(주로 payload) 혹은 구조의 변환과 변환된 Message를 반환하는 책임을 지고 있다.
  • Message의 header내용을 추가/삭제 혹은 변경 할 수 있다.

Filter

역활 및 특징


  • 특정 Message를 다른 channel로 통과 시킬지를 결정한다.
  • Payload의 내용 혹은 header의 존재 여부를 검사하여 boolean값을 반환.
  • true를 반환하면, 다른 channel로 보내지고, false이면 drop된다. (* Message를 drop 시키지 않고 다르게 처리하도록 구현 할 수 있다.)

Router

역활 및 특징


  • Message의 내용 혹은 Message Header내의 metadata에 따라 Message를 특정 channel로 분배 시키는 역활을 수행한다.
  • 다수의 subscriber(channel)로 메세지를 전달할 수 있다.
    Router

Splitter

역활 및 특징


  • 이름처럼 복합적인 내용을 담은 Message의 payload를 분할하여 서로 다른 channel로 전송한다.

Aggregator

역활 및 특징


  • Splitter와는 반대의 객체. 즉, 여러 Message를 수신하여 하나의 Message로 결합 시킨다.
  • Pipeline내에서 주로 마지막에 위치한다.
  • Spring Integration은 아래와 같은 두개의 수집 전략 객체를 제공한다.

    • CorrelationStrategy: 다른 Message가 어떻게 연관관계를 가지는지 결정할 수 있는 객체. 특정 Message와 연관된 Key를 반환.
    • ReleaseStrategy : 언제 Message의 그룹이 완성되는지를 결정하는 객체.

Service Activator

역활 및 특징


  • Service instance와 messaging system을 연결시켜주는 객체.
  • Input Message Channel은 반드시 구성되어야 하지만, output Message channel은 service method의 반환여부에 따라 선택적으로 구성할 수 있다.
    Service Activator
위의 diagram에서 실선은 pollable, 점선은 subscribe이다.

Channel Adapter

역활 및 특징


  • 특정 Message Channel을 다른 system 혹은 다른 전송 매체로 연결시켜주는 endpoint
  • In/outbound에 대한 처리 가능
  • 처리 가능한 전송 System의 예

    • JMS
    • FILE
    • HTTP
    • Web Service
    • Mail

An inbound "Channel Adapter" endpoint connects a source system to a MessageChannel.

위의 diagram에서 실선은 pollable, 점선은 message-driven 이다.

An outbound "Channel Adapter" endpoint connects a MessageChannel to a target system.

위의 diagram에서 실선은 pollable, 점선은 subscribe이다.

Inbound-channel-adapter


  • Converting in-comming message to Message and return to MessageChannel
  • Configure subscription with poller
  • Examples of Configuration in XML
<inbound-channel-adapter ref="source1" method="method1" channel="channel1">
<poller fixed-rate="5000"/>
</inbound-channel-adapter>

<inbound-channel-adapter ref="source2" method="method2" channel="channel2">
<poller cron="30 * 9-17 * * MON-FRI"/>
</channel-adapter>

Outbound-channel-adapter


  • Has message that consuming Message
  • Connected to MessageChannel
  • Examples of Configuration in XML
<outbound-channel-adapter channel="channel1" ref="target1" method="method1"/>
<outbound-channel-adapter channel="channel2" ref="target2" method="method2">
<poller fixed-rate="3000"/>

</outbound-channel-adapter>
<beans:bean id="target1" class="org.bar.Foo"/>
<outbound-channel-adapter channel="channel2" method="method2">
<beans:bean class="org.bar.Foo"/>

</outbound-channel-adapter>

Configuration and @EnableIntegration

Configuration은 XML과 Annotation으로 가능하다. 단, Spring 4.0이상의 경우에만 @EnableIntegration을 사용가능하다.

@EnableIntegration을 사용하려면, Spring 4.0이상, Annotation Configuration이 사용가능해야 한다.

@EnableIntegration


  • @EnableIntegration는 상위 context에서 공통으로 사용되는 component들을 한번만 선언하는 것을 가능하게 해준다.
  • 다음과 같은 Integration의 기반이 되는 component들을 application context에 등록해준다.

    • errorChannelerrorChannelLogginHandler, poller를 위한 taskScheduler, jsonPath등과 같은 내장된 bean들
    • 여러 BeanFactoryPostProcessor ( *BeanFactory의 기능 향상을 위함.)
    • 여러 BeanPostProcessor (* 여러 bean들을 통합 목적으로 사용하기 위함.)
    • Annotation processor (* Messaging Annotation들을 parsing하고 application context에 등록하기 위함.)

@IntegrationComponentScan


  • @ComponentScan과 유사한 기능을 수행 (classpath scanning등, 하지만 Spring integration을 위한 component, annotation들로 제한됨.)

@EnablePublisher

다음의 bean들을 등록하기 위한 생겨난 annotation.


  • channel속성 없이 @Publisher를 사용할수 있게 하는 default-publisher-channel
  • PublisherAnnotationBeanPostProcessor

하나 이상의 @EnablePublisher 사용 시, 모든 @EnablePublisher은 같은 default channel을 위한 값을 가져야 함.

@GlobalChannelInterceptor


  • 전역적인 channel interception을 위한 ChannelInterceptor를 표시하기 위해 생긴 annotation
  • <int:channel-interceptor>xml과 동일한 의미를 가지고 있다.
  • @Component와 함께 class에 선언할 수 있고
  • @Configuration class의 @Bean메서드에 선언할 수 있다.

@IntegrationConverter

다음의 bean들을 표시하기 위해 생긴 annotation


  • Converter
  • GenericConverter
  • integrationConversionService의 대체 bean인 ConverterFactory
  • <int:converter>xml과 동일한 의미.
  • @Component와 함께 class에 선언할 수 있고
  • @Configuration class의 @Bean메서드에 선언할 수 있다.

Correct Lamda expressions JAVA - Basic

Find Invalid Lamda expression

  1. () -> {}
  2. () -> “Raoul”
  3. () -> {return “Mario”;}
  4. (Integer i) -> return “Alan” + i;
  5. (String s) -> {“Iron Man”;}

Answer

4 and 5, they are invalid

Why

Structure of Lamda expression

Lamda expression은 세 부분으로 구성이된다. 첫번째는 아래와 같이 parameter부분이다.
(Type p1, Type p2)
어떤 경우는 Type 부분을 생략할 수 있다. 그 이유는 컴파일러가 타입 추론을 통해 충분히 인자의 타입을 알아 낼 수 있기때문이다.

두번째는 화살표 ->인데 x -> f(x)와 같이 함수에서의 인자를 함수에 전달한다는 의미로 해석하면 될 것 같다.

마지막은 Lamda의 본체(body)부분이다. 이 부분에는 expression 혹은 statement가 올 수 있다.

요약하면 아래와 같은 형태로 Lamda를 작성할 수 있다.


  • (parameters) -> expression
  • (parameters) -> {statements;}

따라서, 위 원칙에 따라 하나씩 살펴보면 1,2,3번은 모두 잘 작성된 Lamda expression들이고, 4번과 5번은 아래와 같이 수정해줘야 정상적인 Lamda expression가 된다.


  • (Integer i) -> {return "Alan" + i;}
  • (String s) -> "Iron Man"

Appendix

Expression와 statement의 차이

Expression의 예, "test"
statement의 예, { return "test";}
위의 두 예에서 볼 수 있듯이, expression은 액션을 취하기보다는 그 자체로 값을 표현하기 위한 것이다. 그리고 expression은 연산자(operators)를 통해 수평적으로 결합이 될 수 있다.
반면 statement는 어떤 액션을 취하기 위한 코드 라인(들)이다. statement는 수직적으로 결합이 된다.

expression은 statement가 될 수 있지만, statement는 expression이 될 수 없다.


TypeScript - 정리 JavaScript

TypeScript

  • Javascipt은 타입이 있긴 하지만 compile이 없는 interpreter 언어라서 실행 시점에 타입에러가 발생이 된다. 이런 단점(?)을 개선하기 위해 TypeScript는 type을 선언할 수 있도록 하고, compile시점에 타입에러등을 잡아낼 수 있는 기능을 제공한다.

  • IDE툴 등을 사용할때, 특정 타입의 정의를 보고 싶을때 소스와 연결된 기능을 TypeScript를 사용하게 되면 사용할 수 있다.

Install

On Mac

Install NodeJs first

install home brew
 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
install NodeJs with homebrew
Install
brew install node
version check
node --version
Install TypeScript
Install
npm install -g typescript

-g 옵션은 global하게 설치하여 모든 사용자가 node를 사용할 수 있게 하는 옵션.

만약 권한 문제로 에러가 난다면 아래의 명령어 실행

sudo npm install -g typescript
version check
tsc -version

Sample project and codes

Project

Name and structure


  1. project name: sample-project
  2. project structure (guide line: STYLE GUIDE)

     sample-project (root)
    - app
    - core

Examples

TypeScript Quick start

Hello World
super simple

  1. file name: greeter.ts
  2. codes

     function greeeter(person){
    return "Hello, " + person;
    }

    var user = "Jane User";
    console.log(greeeter(user));
  3. compile

     tsc greeter.ts
  4. run

     node greeter.js
Type annotations

  1. file name: greeter-type-anno.ts
  2. codes

     function greeter(person: String) {
    return "Hello, " + person;
    }

    var user = [0, 1, 2];
    console.log(greeter(user))
  3. compile

     tsc greeter-type-anno.ts

    ERROR

     Argument of type 'number[]' is not assignable to parameter of type 'String'.

    Why?

  4. correct

     function greeter(person: String) {
    return "Hello, " + person;
    }

    var user = "I am string";
    console.log(greeter(user))
  5. run

     node greeter-type-anno.js
Interface

what? In javascript, interface?


  1. file name: greeter-interface.ts
  2. codes

     interface Person {
    firstName: string;
    lastName: string;
    }

    function greeter(person: Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
    }

    var user = {firstName: "Jane", lastName: "User"};

    console.log(greeter(user));
Classes

OMG!!!

  1. file-name: greeter-class.ts
  2. codes

     class Student {
    fullName: string;
    constructor(public firstName, public middleName, public lastName) {
    this.fullName = firstName + " " + middleName + " " + lastName;
    }
    }

    interface Person {
    firstName: string;
    lastName: string;
    }

    function greeter(person: Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
    }

    var user = new Student("Jane", "M.", "User")
    console.log(greeter(user)); // compile?? error???
  3. generated js file

     var Student = (function () {
    function Student(firstName, middleName, lastName) {
    this.firstName = firstName;
    this.middleName = middleName;
    this.lastName = lastName;
    this.fullName = firstName + " " + middleName + " " + lastName;
    }
    return Student;
    }());
    function greeter(person) {
    return "Hello, " + person.firstName + " " + person.lastName;
    }
    var user = new Student("Jane", "M.", "User");
    console.log(greeter(user));
  4. error

     Argument of type 'Student' is not assignable to parameter of type 'Person'.
    Property 'lastName' is missing in type 'Student'.

Project config

tsconfig.js

tsconfig.json

프로젝트 폴더 바로 하위에 tsconfig.json을 위치 시키게 되면, Typescript 프로젝트가 된다.
tsconfig.json에 포함되는 내용은 아래와 같다.

  • Root files (e.g. app.ts, index.html etc…)
  • compiler options

tsconfig.json을 이용하는 방법은 아래와 같다.


  • tsc 명령어에 아무런 compile하고자하는 파일을 주지 않을 경우, compile는 tsconfig.json을 찾아 tsconfig.json에 명시된대로 compile을 하게된다.
  • tsc 명령어에 —project option을 주고 프로젝트 경로를 option값으로 주는 경우, 주어진 경로 하위에 위치한 tsconfig.json을 찾아 tsconfig.json에 명시된대로 compile하게 된다.

tsconfig.json examples

tsconfig.json은 JSON format의 객체 형태로 기술된다.

references
compiler options

  • compiler options
  • source map

    • 압축 혹은 난독화되어 배포되는 최종 소스와 원본 소스를 연결 시켜주는 방법
    • Spec

Files

  • files: 상대 및 절대경로의 파일들을 배열 형태로 선언할 수 있다.
  • include (exclude):아래와 같은 파일 패턴 형태를 지원하며, 패턴과 일치되는 모든 파일을 포함(제외)한다.

    • * 모든 파일을 대상으로 한다(파일 경로 구분자도 포함한다.)
    • ? 파일명 중 하나 이상의 character가 일치하는 파일을 대상으로 한다.(파일 경로 구분자도 포함한다.)
    • */ 일치되는 모든 하위 디렉토리를 대상으로 한다. (e.g. “src/\*/*“ )

Searching target files

  • * or .* : Typescript에서 지원되는 확장자로 선언된 파일들만 포함(제외)한다. (e.g. .ts, .tsx, .d.ts) 그리고 alloJs옵션이 true로 설정된다면 .js, .jsx파일도 포함하게 된다.
  • files, include 둘 다 tsconfig.json에 명시 하지 않았다면, compiler는 모든 Typescript 확장자(.ts, .tsx, .d.ts)를 가진 파일들을 모두 포함 시킨다. 단 exclude에 명시된 파일들은 제외된다.
  • files, include 둘 다 사용된 경우, 각각의 옵션에 명시된 파일들을 모두 포함 시킨다.
  • outDir에 명시된 디렉토리는 기본적으로 대상 파일에서 제외되지만, 명시적으로 files에 선언한다면 포함 시킬 수 있다.
  • include에 포함되도록 설정되도록 설정된 파일 중 일부를 exclude로 제외 시킬 수 있다.
  • files에 포함된 파일은 exclude에 선언이 되어 있더라도 제외되지 않는다.
  • excluce옵션은 기본적으로 아래의 디렉토리들을 제외 시킨다.

    • node_modules: nodeJs components
    • bower_components: application libraries
    • jspm_packages: package manager that loads any module format(ES6, AMD, CommonJs and globals), jspm homepage
    • outDir

  • include dependencies automatically: files 혹은 include에 선언된 파일이 다른 파일을 참조할 경우, 참조되는 파일을 자동으로 포함 시킨다. 예를 들어, A.ts가 B.ts을 참조한다면, B.ts가 files 혹은 include에 선언되어 있지 않더라도 compile시 자동으로 찾아 컴파일하게 된다.
@types, typeRoots 그리고 types

Typescript에서 제공하는 기본 타입들(all visible “@types”)은 자동으로 컴파일 시 로딩된다.

examples
using files
{
"compilerOptions" : {
"module": "commonjs",
"noImplicitAny": true, //not allow declarations with Any type, default: false
"removeComments": true, // Remove all comments except copy-right header comments beginning with /*! , default:false
"preserveConstEnums": true, // do not erase const enum declarations in generated code.
"sourceMap": true // source map 사용여부 활성화
},
"files": [
//....
]
}
using include, exclude
{
"compilerOptions": {
"module": "system",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "../../built/local/tsc.js",
"sourceMap": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}

FP - Basic concepts 정리 Scala

Functional Programming

Basic Concepts

Mathematical functions

수학적으로 함수는 아래와 같이 표현할 수 있다. 아래와 같은 표현 및 개념은 실제로 Functional Programming에서 사용되며, 코드 작성 시에도 중요하게 사용된다. 
  • x -> f(x)
  • f(x) = x*2
  • f:X -> Y
  • y = f(x)

Side Effect

Description (or Definition)

Application이 실행되는 환경(Ram, disk, IO etc…)내에서 정의되고 사용되는 객체 혹은 변수의 상태(혹은 값)이 변경되는 것을 의미한다.
예를 들어, 아래와 같은 것들이 side effect일 수 있다.


  • 변수의 값 변경
  • 디스크에 값을 쓰는 것
  • 사용자 interface에서 버튼의 활성/비활성

그러면 side effect는 나쁜 것인가? 좋은 것인가?라는 의문이 생길 수 있다. side effect는 좋지도 나쁘지도 않다. 다만 applicaton(혹은 function)을 작성하는 프로그래머가 이를 고려하지 않을 경우에는 의도 하지 않은 결과가 나올 수 있습니다. 때문에 되도록 side effect가 발생하지 않도록 하는 것이 좋지만, side effect를 피할수 없는 경우에는 반드시 이를 고려해서 application(혹은 function)등을 작성해야 합니다.

Code examples

With side effect
var x = 0;
x = 3 + 4; // this statement has side effect
Without side effect
def f = 3 + 4

Pure Functions(순수 함수)

Pure function은 Input에의해 output(반환값)이 결정되는 함수를 의미한다. 이 과정에서 어떤 부작용(side effect)도 발생하지 않아야 한다.
이는 Math.cos(x)와 같이 동일한 x에 대해 항상 같은 값을 반환하는 수학적 함수와 동일한 개념이다. 그리고 주어진 x의 값을 절대 바꾸지 않으며, 로그 파일이 기록하거나, 네트워크 요청을 보내는 등은 하지 않는다.

이런 함수가 반환값의 계산 이외의 행위를 한다면, 그 함수는 impure(순수하지 않은) function이다. 따라서 특정 함수에서 impure function을 호출한다면 해당 함수 또한 impure function이다.

위에 주어진 함수의 경우 pure function이기때문에 해당 함수가 반환하는 값으로 대체되어도 무방하다. 즉, Math.cos(Math::PI)는 -1로 대체해버릴 수 있는 것이다. 이런 속성을 referential transparency라고 한다.

pure function의 일반적인 규칙은 아래와 같다.


  • 주어진 input이외의 것으로 결과값이 바뀌어서는 안된다.
  • 주어진 input의 값을 절대 바꿀 수 없다.
  • 함수 밖, 함수가 실행되는 context상에 존재하는 것을 바꿀 수 없다.
  • 주어진 input이 동일할 경우, 항상 동일한 결과값을 반환해야 한다.

위에서 사용한 Math.cos(Math::PI)룰 -1로 바꿀 수 있는 것을 볼때, pure function은 값(데이터)로 생각할 수 도 있다. 그리고 pure function들은 일급 객체(first class citizens)이다. 즉, pure function들은 변수처럼 다른 함수에 주어질 수 있고 새로운 기능(함수)를 작성하는데 부분적으로 적용될 수 도 있다.

즉, 함수는 재사용가능하고 조립(? 조합) 가능해야 한다.

Referential Transparency

Description (or Definition)

공식적인 정의는 없는 것으로 보이지만, 보통 어떤 상황에서든지 함수 등이 동일한 입력값에 대해 동일한 결과값을 도출하는 것을 의미한다.
다음의 예는 referential transparency 하다라고 할 수 있다.

def f = 2 + 2

위의 예제는 너무도 간단해서 실제로 사용하기에는 무리가 있다. 다음의 예제를 살펴보면

def even(number: Int): Boolean = number %2 == 0
def f = (1 to 10).filter(even)

위의 예제에 선언된 even과 f는 동일한 입력값에 대해 동일한 결과를 반환한다.

ref sites

Comments

referential transparency

참조 투명성, 관계 투명성으로 번역되고 있다. 하지만 원문 자체로 기억하는 것이 좋을 것 같다.

Scala Code
Math.cos(Math.PI)

Immutable Values(불변 객체, 값)

OOP와 같이 Stateful한 언어들로 동시성을 제어하는 것은 어려우며 다음과 같은 것들을 사용해서 코드를 안전하게 작성해야 한다.


  • mutexes
  • locks
  • semaphores
  • access constraints

하지만 FP에서는 가변(Stateful, mutable)한 것을 허용하지 않는다. 즉, 객체 혹은 값이 한번 설정되면 절대 바꿀 수 없다.

Monads

Monads, 생소한 용어, 개념이다. Monads는 일종의 컨테이너라고 생각할 수 도 있다. 복잡한 설명보다 아래의 코드로 대충 감을 잡는 것이 좋을 것이다.

def someComputation(): Option[String] = ...
val myPossibleString = someComputation()

위의 코드를 보면 someComputation의 반환 타입은 Option[String]으로 되어있다. 이는 결과가 있을 수도, 없을 수도 있다는 의미인데, NullPointerException을 유발할 수 있는 Null을 반환하는 것이 아닌 것이다.
someComputation이 반환하는 Option[String]은 Monads라고 할 수 있다. 왜냐면 someComputation이 반환하는 값이 있을 경우, Option은 내부에 String 타입의 값을 포함하는 컨테이너 역활을 하기 때문이다.

아래의 자바 코드와 비교해보자

String myPossibleString = someComputation();

if(myPossibleString == null){
//do something
}
return myPossibleString.toUpper();

위의 자바코드를 보면 someComputation이 반환하는 값에 대한 Null 확인을 하고 있다. 그런데 위의 코드를 아래와 같이 함수형으로 재 작성할 수 있다.

someComputation().map(_.toUpper)

Monads로 매핑을 하기 위해선, 아래와 같이 Some 혹은 None으로 결과값을 반환해야 한다.

Some("test")
//or
None

Monads에 대한 자세한 설명은 아래의 동영상을 통해 확인 할 수 있다.
Brian Beckman’s Monad

References


Scala Tutorial 번역 (XML Proccessing) Scala

XML Processing

Scala를 사용해서 쉽게 XML문서를 생성 및 파싱 그리고 처리할 수 있다. Scala에서 XML 데이터를 표현하는 방법은 아래와 같다.


  • Generic data 표현 (xml 자체를 Scala코드처럼 사용할 수 있는 표현법으로 이해된다.)
  • Data-specifi data 표현 (데이터에 초첨을 맞춘 표현법으로 이해된다.)

아래와 같은 HTML(xml과 동일한 표기법이다)이 주어졌을때 이를 Scala 코드로 옮겨보자

Runtime Representation

HTML(XML)
<html>
<head>
<title>Hello XHTML world</title>
</head>
<body>
<h1>Hello world</h1>
<p><a href="http://scala-lang.org/">Scala</a> talks XHTML</p>
</body>
</html>
Scala codes
object XmlTest1 extends App {
val page =
<html>
<head>
<title>Hello XHTML world</title>
</head>
<body>
<h1>Hello world</h1>
<h1>Scala is cool</h1>
<p><a href="http://scala-lang.org/">Scala</a> talks XHTML</p>
</body>
</html>
println(page.toString());

println("type of page: "+ page.getClass)
println("body h1 text " +( page \\"body" \\ "h1").head.text) // body 하위의 첫번째 h1의 text를 받아온다.
}

위의 코드를 실행하게 되면 HTML의 내용이 그래도 표시되게 된다. 또한 결과를 보면 알겠지만 page의 타입은 scala.xml.Elem이다.

또한 아래의 예제처럼 Scala 코드와 XML을 함께 섞어서 사용할 수 있다.

object XmlTest2 extends App {
import scala.xml._
val df = java.text.DateFormat.getDateInstance()
val dateString = df.format(java.util.Calendar.getInstance().getTime())
def theDate(name: String) = {
<dateMsg addressedTo={name}>
Hello, {name}! Today is {dateString}
</dateMsg>
}
println(theDate("John").toString())
}
output
<dateMsg addressedTo="John">
Hello, John! Today is 2017. 1. 24
</dateMsg>

Data Binding

많은 경우 XML문서에 대한 DTD에 해당하는 Scala class들을 선언하여 XML문서를 파싱 및 처리 그리고 저장 한다. Scala는 DTD문서들을 Scala class들로 변환할 수 있는 사용하기 쉬운 tool들을 제공한다. 이런 툴 중 하나인 schema2src툴에 대한 문서는 Burak’s draft scala xml book을 참조하면 된다.


Scala Tutorial 번역 (Singleton object, Companion) Scala

Singleton Objects

Scala는 Java보다 좀 더 객체지향이라고 할 수 있다. 그 이유는 Scala는 static member를 가질 수 없기때문이다. static member대신에 singleton 객체를 제공한다.

singleton 객체를 만들때 class키워드 대신 object키워드를 사용하여 정의 한다. singleton 객체는 new 키워드를 사용하여 인스턴스를 만들 수 없기때문에, 생성자에 인자들을 넘길 수 없다. 이것은 약간 자바랑 다를 수 있다. 자바에서는 singleton class정의시에 인자를 받을 수 있는 생성자를 정의할 수 있고, newInstance 혹은 createInstance등과 같은 static method를 통해 해당 생성자를 호출 할 수 있기 때문이다. (* 좋은 방법은 아니다. 권장하지 않음)

package test
object Blah {
def main(args: Array[String]): Unit = {
val l = List(1,2,3,4,5)
println(sum(l))
}

def sum(l: List[Int]) : Int = {
l match {
case x :: Nil => x
case x :: xs => x + sum(xs)
case Nil => 0
}
}
}

위의 예제에 정의된 sum 메서드는 전역으로 사용 가능하다. 즉, test.Blah.sum으로 import하거나 참조되어 사용 가능하다.

Companions

대부분의 singleton 객체들은 singleton객체와 동일한 이름을 가지는 class 혹은 trait와 함께 정의한다. 그리고 반드시 동일한 source 파일에 정의되어야 한다. 그리고 다른 객체와는 다르게 class 혹은 trait에 private로 선언된 속성 혹은 메서드에 접근 할 수 있다. 이렇게 동일한 이름으로 선언된 object와 class는 싱글 톤 객체(object)는 클래스의 컴패니언 객체라고 불리며, 클래스는 객체의 컴패니언 클래스라고 불린다.
Sclacdoc은 companion의 표기를 다음과 같이 하고 있다.


  • “C” : jump to companion class
  • “O” : jump to companion object

(* Companions는 자바로 치자면 static member 혹은 method라고 할 수 있을 것이다. )

class IntPair(val x: Int, val y:Int){
private val sum = x + y;
override def toString = "(" + x + ", " + y + ")"
}
object IntPair {
import math.Ordering
/*
IntPair(1, 1) 이렇게 생성자 비슷하게 호출할 경우, 아래의 apply메서드가 호출된다. 그리고 IntPair class 객체를 아래와 같이 생성하여 반환한다.
*/
def apply(x: Int, y: Int) = {
println("apply of object, x: "+ x + ", y: "+ y)
val intPair = new IntPair(x, y)
intPair // return 키워드가 생략된 것이다.
}
/*
http://www.scala-lang.org/api/current/scala/util/Sorting$.html
API정의에서 보듯이 Sorting.quickSort는 Ordering을 필요로 한다.
quickSort메서드가 필요로 하는 Ordering은 아래의 메서드가 제공을 해주는 것이고,
아래의 메서드는 implicit 키워드를 사용해서 정의되었기 때문에, 런타임에 알아서, 자동으로 호출이 된다. 반환타입등으로 추론을하여 호출하여 주는 것으로 생각된다.
그렇기 때문에 아래의 함수명은 아무렇게나 지어도 상관없다. 이름으로 호출하는 것이 아니라 반환 타입등으로 추론하여 호출하기 때문이다.
*/
implicit def ipord: Ordering[IntPair] = {
Ordering.by(ip => {println("ip:" + ip); ip.sum})
}

}

object Test {
import IntPair._
import scala.util.Sorting
def main(args: Array[String]): Unit = {
val list = Array(IntPair(3, 3), IntPair(1, 1), IntPair(2, 1))
println(list.mkString(","))
Sorting.quickSort(list)
println(list.mkString(","))
}
}

Note for Java programmers

static은 Scala에서는 사용되지 않는 키워드이다. singleton 객체에 포함되는 모든 member 그리고 class들 모두 static하게 접근할 수 있다. 위에서 설명한 것처럼 java에서 사용하던 static member, class들은 Scala에서는 companion으로 정의하여 사용할 수 있다. 아래 예제와 같은 패턴으로 사용하는 것이 일반적이다.

class X {
import X._

def blah = foo
}

object X {
private def foo = 42
}

위의 예제에서 한번 더 확인 할 수 있는 것이, private으로 선언된 member를 아무렇지 않게 접근할 수 있다. 이는 class와 class의 companion은 서로 친한 친구같은 사이여서 서로의 member등에(private이어도)접근할 수 있다. 그렇지만 친구에게도 공개하고 싶지 않은게 있는 것처럼 companion에게도 공개하지 않을려면 아래와 같이 선언하면 된다.

private[this] blah

Java에서 companion으로 선언된 singleton object의 메서드를 사용할려면 companion class에 동일한 이름을 가지는 메서드(static forwarder)를 선언해서 호출할 수있다. 그 이외의 member들은 아래와 같이 접근할 수 있다.

X$.MODULE$.[method or field name]

참고

Companion

static forwarder


1 2 3 4 5 6 7 8 9 10 다음


side adsense

adSense 900*70