성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>clojure와 C#을 통해 이해하는 Sequence와 Vector 형식의 차이점</h1> <p> 아래와 같은 질문이 있군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > clojure에서 list와 vector의 차이점은? ; <a target='tab' href='http://www.slipp.net/questions/194'>http://www.slipp.net/questions/194</a> </pre> <br /> 어찌 보면, 아래의 글도 역시 그 차이점을 이해하지 못하는 것과 관련이 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Java의 함수형 프로그래밍이 생각보다 위험하지 않은 이유 ; <a target='tab' href='http://justhackem.wordpress.com/2014/06/19/why-functional-programming-in-java-is-not-dangerous/'>http://justhackem.wordpress.com/2014/06/19/why-functional-programming-in-java-is-not-dangerous/</a> </pre> <br /> 그래도 C# 언어를 공부한 분이라면 이 차이를 쉽게 이해할 수 있습니다. 즉, 아래와 같이 쉽게 설명할 수 있기 때문입니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 언어상으로 보면 Sequence는 IEnumerable이고, Vector는 IList 자료형 </pre> <br /> 사실 <a target='tab' href='http://www.yes24.com/Product/Goods/97314203'>제가 쓴 책에서도 580 페이지의 "7.4 yield return/break" 구문</a>을 설명하면서 이에 대한 예를 들고 있습니다. 이것 때문에 여러분들이 책을 구매하시기에는 좀 그러하니... 이 글에서 다시 설명해 보겠습니다. ^^<br /> <br /> 제가 책에서 든 예제는 '자연수 집합'을 프로그램에서 표현하는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 집합 N = { 1, 2, 3, 4, ... } </pre> <br /> 이는 무한히 반복되기 때문에 코드로 표현하기가 애매합니다. 가령 다음과 같이 만들면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { uint [] integers = GetIntegers(10); for (int i = 0; i < integers.Length; i ++) { Console.WriteLine(integers[i]); } } static <span style='color: blue; font-weight: bold'>uint[]</span> GetIntegers(uint end) { List<uint> list = new List<uint>(); for (uint i = 1; i < end; i++) { list.Add(i); } return list.ToArray(); } </pre> <br /> 이 함수가 자연수를 표현한다고 보기에는 무리가 있습니다. 시작 지점부터 끝 지점까지를 명시해야 하고 심지어 이렇게 반환받은 자연수 배열을 호출 측에서 모두 꺼내쓴다고 장담할 수도 없습니다. 다시 말하면, 어떤 경우에는 쓸데없이 메모리만 할당해서 반환한 요소도 존재할 수 있는 것입니다.<br /> <br /> 바로 여기에 IEnumerable을 도입하면 자연스럽게 자연수 표현이 가능해 집니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { foreach (int elem in Integers()) { Console.WriteLine(elem); } } static <span style='color: blue; font-weight: bold'>IEnumerable<uint></span> Integers() { uint start = 1; while (true) { <span style='color: blue; font-weight: bold'>yield return start++;</span> } } </pre> <br /> 위의 프로그램을 돌리면 여러분이 컴퓨터를 끄는 순간까지 무한한 자연수의 집합을 출력합니다. 물론 uint의 한계값이 0xffffffff이므로 진정한 한계를 없애려면 <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/api/system.numerics.biginteger'>BigInteger</a>를 사용하시면 됩니다. (그 이후에는 시간과 메모리가 장벽이고!)<br /> <br /> 한 가지 알아두어야 할 것은, Vector형은 IEnumerable로 쉽게 변환이 가능합니다. 가령 C#의 경우 List 자료 구조가 IEnumerable도 함께 구현하고 있습니다. 왜냐하면 순차적(sequential)으로 열람할 수 있다면 IEnumerable 구현이 가능하기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class List<T> : IList<T>, ICollection<T>, <span style='color: blue; font-weight: bold'>IEnumerable<T>, IEnumerable,</span> IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T> </pre> <br /> clojure의 경우에도 vector 자료형을 seq 함수를 통해 sequence로 쉽게 변환할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > user=> <span style='color: blue; font-weight: bold'>(def x [1 2 3])</span> #'user/x user=> <span style='color: blue; font-weight: bold'>(seq x)</span> (1 2 3) </pre> <br /> 표현을 달리해서 말하자면, 특정 요소가 있을 때 vector는 random access가 가능한 자료형이고, sequence는 순차적으로 그 요소가 나올 때까지 처음부터 열거해야 하는 자료형입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그럼 이해가 되셨나요? ^^ 좀 더 이해를 돕기 위해 "<a target='tab' href='http://justhackem.wordpress.com/2014/06/19/why-functional-programming-in-java-is-not-dangerous/'>Java의 함수형 프로그래밍이 생각보다 위험하지 않은 이유</a>" 글에서 나온 코드를 C#과 clojure 버전으로 구현해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > (take 25 (squares-of (integers))) </pre> <br /> 기본 clojure에는 integers와 squares-of 함수가 없기 때문에 이를 만들어 보면 됩니다. 우선 integers를 정의해 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > (defn integers [] (iterate inc 1)) </pre> <br /> 오... 멋지군요. ^^ C#의 yield return을 이용한 구문보다 훨씬 간단합니다. 이 함수를 clojure REPL에서 실행해 보면 무한히 반복되는 자연수 출력을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > user=> <span style='color: blue; font-weight: bold'>(integers)</span> (1 2 3 4 5 ...[REPL에서는 integers 함수의 평가 결과가 출력되므로, 결과적으로 무한히 자연수 출력]...) </pre> <br /> clojure의 integers 함수를 C#으로 구현하는 방법은 위에서 이미 살펴봤으므로 생략합니다.<br /> <br /> 그다음 squares-of 정의를 구현해 볼텐데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > user=> <span style='color: blue; font-weight: bold'>(defn squares-of [x] (* x x))</span> #'user/squares-of </pre> <br /> 위의 구현이 올바를까요? clojure는 타입 추론 기능이 있어서 (* x x)의 코드로 인해 x 인자의 형식을 단일 값으로 판단합니다. 실제로 squares-of의 반환값 형식을 통해 이것이 sequence가 아님을 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > user=> <span style='color: blue; font-weight: bold'>(class (squares-of 5))</span> System.Int64 </pre> <br /> 따라서 우리가 원래 의도했던 "(squares-of (integers))" 코드를 수행하면 다음과 같이 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > user=> <span style='color: blue; font-weight: bold'>(squares-of (integers))</span> InvalidCastException Unable to cast object of type 'clojure.lang.Cons' to type 'System.IConvertible'. System.Convert.ToInt64 (:0) </pre> <br /> 왜냐하면, integers 함수는 Sequence(clojure.lang.Cons)를 반환하는 반면, squares-of 함수는 입력 인자로 정수값 하나를 기대하기 때문에 그로 인한 불일치가 발생하는 것입니다. 이 오류를 해결하려면 squares-of 함수가 Sequence를 입력받아 처리하는 구조로 바뀌어야 합니다. 그럼 이렇게 구현하면 되겠지요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > (defn squares-of [x] (map #(* % %) x)) 또는 (defn squares-of [x] (map (fn[w] (* w w)) x)) 또는 (defn square [x] (* x x)) (defn squares-of [x] (map square x)) </pre> <br /> 이제 integers와 squares-of를 결합하면 무한한 자연수를 열거하면서 그것의 각각을 제곱해주는 결과가 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > user=> (defn integers [] (iterate inc 1)) #'user/integers user=> (defn squares-of [x] (map #(* % %) x)) #'user/squares-of user=> (squares-of (integers)) ; 이 함수 실행 이후 식이 평가되면서 무한 자연수 제곱 결과 출력 (1 4 9 16 25 ...[생략]... ...) </pre> <br /> 이것을 C#으로 구현하면서 비교해 볼까요? 일단 integers는 구현했으니 squares-of 함수를 구현해 보면 이렇습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { foreach (int elem in <span style='color: blue; font-weight: bold'>SquaresOf(Integers())</span>) { Console.WriteLine(elem); } } static IEnumerable<uint> SquaresOf(IEnumerable<uint> elems) { foreach (uint elem in elems) { yield return (elem * elem); } } </pre> <br /> C# 확장 메서드 기능을 이용하면 이런 식의 호출도 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { foreach (int elem in <span style='color: blue; font-weight: bold'>Integers().SquaresOf()</span>) { Console.WriteLine(elem); } } static IEnumerable<uint> SquaresOf(<span style='color: blue; font-weight: bold'>this IEnumerable<uint> elems</span>) { foreach (uint elem in elems) { yield return (elem * elem); } } </pre> <br /> 사실, 일반적인 프로그램 세계에서 무한을 반환하는 함수는 버그에 가까운 기능입니다. 그래서 대개는 무한을 표현한다 해도 다음과 같이 일정 수의 요소만 취하는 기능이 꼭 필요하게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > user=> <span style='color: blue; font-weight: bold'>(take 25 (squares-of (integers)))</span> (1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400 441 484 529 576 625) </pre> <br /> 즉, 자연수를 제곱한 sequence 중에서 처음부터 25개의 요소만 가져오는 것입니다. C#도 Take 메서드를 제공하기 때문에 별다른 코드없이 유사하게 구현할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > foreach (int elem in <span style='color: blue; font-weight: bold'>Integers().SquaresOf().Take(25)</span>) { Console.WriteLine(elem); } </pre> <br /> sequece, vector 형식은 clojure뿐만 아니라 F#과 같은 함수형 언어에도 (이름만 다를 뿐) 동일하게 나오므로 한번 익혀두시면 다른 함수형 언어의 자료 구조를 이해하는 데 도움이 되실 것입니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로 clojure의 경우 integers를 함수가 아닌 var에 바인딩하는 것도 가능합니다. 그런 경우에는 함수 평가를 하는 것이 아니기 때문에 integers 호출에 괄호를 사용할 필요가 없어 다음과 같이 바뀝니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>(def integers (iterate inc 1))</span> (defn squares-of [x] (map #(* % %) x)) (take 5 (squares-of <span style='color: blue; font-weight: bold'>integers</span>)) </pre> <br /> 또는, squares-of를 sequence를 받지 않고 단일 요소로 받도록 하는 경우 map 함수를 이용해 우회해서 동일한 효과를 얻을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > (def integers (iterate inc 1)) (defn squares-of [x] (* x x)) (map squares-of (take 5 integers)) </pre> <br /> 이런 건 그냥... 표현의 방식일 뿐이니.<br /> <br /> 사실 절차형 프로그래밍에서 vector와 sequence의 구분이 크게 중요하지 않은 것은 그런 거 몰라도 어렵지 않게 프로그램할 수 있기 때문입니다. 가령, IEnumerable을 쓰지 않고 그냥 다음과 같이 무한 자연수를 표현해도 무방하지요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { Integers int32 = new Integers(); while (true) { Console.WriteLine(int32.GetNext()); } } class Integers { uint i = 1; public uint GetNext() { return i ++; } } </pre> <br /> 결과적으로 봤을 때 다를 것이 없습니다! 괜히 어려운 거 좋아하는 사람들이 IEnumerable을 들이대는 거라고 생각해도 좋습니다. ^^<br /> <br /> (마지막으로... 이런 글 쓰면 제가 clojure 좀 할 줄 안다고 생각하실 수도 있는데, 완전 초보입니다!)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1358
(왼쪽의 숫자를 입력해야 합니다.)