본문으로 건너뛰기

"programming" 태그로 연결된 3개 게시물개의 게시물이 있습니다.

모든 태그 보기

요즘 유인동님의 신간인 멀티패러다임 프로그래밍을 읽고 있다. 유인동님의 함수형 강의는 같은 팀 동료 분이 추천해주셔서 들었던 기억이 있는데, 책이 나왔다는 소식에 반가운 마음 + 흥미로운 주제에 구매하게 됐다.

참고로 책의 예제 코드는 이 레포에서 확인할 수 있다. 어떤 디렉터리에 있는지 찾느라 좀 헤매서 남겨본다.

초반 챕터에서 반복자(Iterator) 패턴에 대해 다루는데, 개인적으로 한국어로 iterator를 설명하는 자료 중 가장 명쾌하게 이해할 수 있는 설명이라고 느꼈다. 또한 이 챕터에서 설명하고 있는 내용이 멀티패러다임이라는 주제를 흥미롭게 소개해주는 것 같아 가벼운 글로 소개해보고자 한다.


기본적으로 반복자 패턴은 선언과 동시에 평가되지 않고, 필요한 시점에 평가할 수 있다. 예제를 통해 살펴보자.

const array = [1, 2, 3, 4, 5];
array.reverse();
console.log(array[0], array[1]); // 5 4

반복자를 사용하지 않는 일반적인 방법은 기본적으로 즉시 평가가 일어나며 원본 배열의 mutation이 발생한다. 물론 원본을 복사해둘 수도 있지만 메모리 효율적인 방법은 아니다.

const array = [1, 2, 3, 4, 5];

function reverse(arrayLike) {
let idx = arrayLike.length;
return {
next() {
if (idx-- >= 0) {
return { value: arrayLike[idx], done: false };
} else {
return { value: undefined, done: true };
}
},
};
}

const iterator = reverse(array);
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: 4, done: false }

이 예제에서는 idx라는 변수를 갖는 객체를 리턴하는 함수를 만들었다. reverse()를 통해 객체를 선언하는 시점이 아닌 next()가 호출되는 각각의 시점에 평가가 이루어진다.

즉, 평가는 필요한 시점에 필요한 만큼 효율적으로 이루어진다.

배열의 길이가 커지거나 성능에 대한 요구사항이 엄격해질수록 지연 평가는 큰 장점이 될 수 있다. 성능과는 별개로 원본 배열을 immutable하게 다룰 수 있다는 점에서의 장점도 있다.

여기서 재밌는 지점은 반복자 패턴은 전통적인 객체지향 디자인 패턴이라는 점이다. 그러나 함수가 일급인 자바스크립트에서는 예시 코드에서처럼 함수형 프로그래밍의 장점이 결합되어 더 큰 시너지를 낼 수 있다.

여기서 generator를 사용하여 같은 구현을 명령형 코드로도 구현할 수 있다.

function* reverse<T>(arrayLike: ArrayLike<T>): IterableIterator<T> {
let idx = arrayLike.length;
while (idx--) {
yield arrayLike[idx];
}
}

const array = ["A", "B", "C", "D", "E", "F"];
const reversed = reverse(array);

console.log(reversed.next().value); // F
console.log(reversed.next().value); // E
console.log(reversed.next().value); // D

앞선 reverse 함수는 idx라는 멤버를 가지는 객체를 생성함으로서 반복자를 구현했다. 이 예시에서는 generator를 사용하여 조건문을 사용해 명령형 코드로 같은 기능을 구현했다.

이처럼 다양한 패러다임의 유연한 선택과 그들간의 조합을 통해 문제에 적합한 해결책을 찾을 수 있다.

여기까지 읽고 흥미를 느꼈다면 책을 읽어보는 것도 좋을 것 같다. 초반부는 앞서 링크한 레포에서 읽어볼 수도 있다.

나는 요즘 넘 재미나게 읽고 있어서 추천~


부록

새롭게 추가된 Iterator.prototype의 메서드들로 (map, filter, reduce 등) 반복자에서 지연 평가 로직을 더 간단하게 구현 할 수 있다. 알아두면 좋을 것 같다.

악기 연습할 때 메트로놈을 사용하는데, 웹에서 써본 메트로놈들은 기능적으로 완벽하게 마음에 드는 경우가 잘 없었다.

특히 볼륨 조절이 없는 경우가 많아서 그냥 유튜브에서 xxx BPM metronome 같은 영상을 틀고 연습하는 경우가 많았다.

그런데 유튜브 영상은 BPM을 조절하려면 다른 영상을 서치해서 재생하는 과정이 너무 번거로워서 이럴거면 하나 만들어보자 싶어서 만들었다.

copilot edit 기능을 적극적으로 활용했다. 웹 오디오 API에 대한 지식이 거의 없었는데, copilot이 관련된 코드를 잘 만들어줘서 큰 도움이 됐다. 만들어진 코드를 수정하다 보니 오디오 API에 대해서 이해가 되는 부분도 꽤 있었다.

첫 삽 부터 작동하는 프로토타입을 완성하는 데 2~3시간 정도 사용한 것 같다. 그 이후로도 종종 들여다보고 리팩터 하거나 기능을 추가해주고 있다.

아래 링크에 배포해 두었다!

https://jeonjaewon.github.io/web-metronome/

이건 내비게이션에 필요 없어요

업무하면서 도로명과 지번주소가 혼재되어 있는 데이터를 다룬 적이 있는데, 예를 들면 이런 데이터였습니다.

  • 주소1: 서울특별시 서초구 반포동 20-43 반포자이아파트 101동 101호
  • 주소2: 서울특별시 영등포구 국회대로62길 9 (여의도동)

예시의 주소 1의 경우 지번주소, 주소2는 도로명주소 입니다.

문제는 이 데이터를 토대로 내비게이션 API에 요청을 보내야 했는데, 저희가 사용 중인 API에서는 상세 주소에 대한 처리가 되어 있지 않아서 간혹 응답을 내리지 않는 경우가 있었습니다.

주소 3을 예로 들면 상세주소에 해당하는 반포자이아파트 101동 101호에서 101호는 사실 길찾기에 필요한 정보는 아닙니다. 이걸 포함시켜서 요청하면 길을 제대로 찾지 못하더라구요.

이해를 돕기 위해 주소를 두 가지로 분류해 보겠습니다.

  1. 배송을 위한 주소
  2. 길찾기를 위한 주소

1과 2가 같은 경우도 분명 있지만, 공동주택 등의 경우 정확한 호수를 찾기 위한 상세주소가 더해지기 때문에 표기에 차이가 생깁니다.

제가 겪은 문제의 경우 2만 취급하는 API에 1을 보냈기 때문에 발생한 문제였습니다.

그렇다면 문자열 뒷부분의 상세주소만 제거해서 요청하면 되겠다는 생각이 듭니다. 이제 이 문자열들의 어디서부터가 상세주소인지를 찾아야 합니다.

그렇다면 섞여 있는 도로명주소와 지번주소를 각각 다르게 파싱해야 할까요? 애초에 별도의 칼럼이 없는데 문자열만 보고 이 둘을 구분할 수는 있을까요?


모르겠을 땐 문서를 보기

도로명주소 안내 시스템에 가보면 도로명 주소 소개 pdf 파일을 배포하고 있습니다.

Example banner

눈여겨보아야 할 부분은 가운데의 동/리 지번도로명 건물번호에 대응된다는 점입니다. 자세한 도로명 건물번호 규칙은 아래와 같은데요.

Example banner

이 부분이 중요합니다. 도로명은 붙여 쓰고, 도로명과 건물번호는 띄우기 때문에 이는 기존의 동/리 지번과 공백을 기준으로 호환이 된다고 볼 수 있습니다.

무슨 말인지 앞서 보았던 예시를 다시 보겠습니다.

  • 주소1: 서울특별시 서초구 반포동 20-43 반포자이아파트 101동 101호
  • 주소2: 서울특별시 영등포구 국회대로62길 9 (여의도동)

지번주소의 동/리 지번반포동 20-43, 도로명주소의 도로명 건물번호국회대로62길 9에 해당하는 것을 알 수 있습니다. 그리고 이 주소들은 모두 가운데에 공백 문자를 1개 포함한 문자열입니다.

이 주소들 이후부터는 상세주소가 나오는 것을 확인할 수 있습니다.

즉, 도로명이냐 지번이냐에 관계 없이 단순히 문자열을 공백을 기준으로 앞에서부터 4번 취하면 상세주소를 제거한 주소를 얻을 수 있습니다.

"서울특별시 서초구 반포동 20-43 반포자이아파트 101동 101호"
.split(" ")
.slice(0, 4)
.join(" ");
// '서울특별시 서초구 반포동 20-43'

"서울특별시 영등포구 국회대로62길 9 (여의도동)"
.split(" ")
.slice(0, 4)
.join(" ");
// '서울특별시 영등포구 국회대로62길 9'

이제 이 문자열이 신주소인지 구주소인지와는 관계 없이 간단한 방법으로 상세주소를 제거할 수 있습니다.