# 함수형 언어의 이점
# 불변성(Immutable)
클로저는 함수형 언어입니다. 따라서 대규모 응용프로그램을 작성하기를 겨냥하고 있습니다. 애플리케이션이 성장함에 따라 구성 요소를 분리하여 추론할 수 있어야 합니다. 반대로, 테스트 가능하고 재사용 가능한 구성 요소로 코드를 구축하는 것은 많은 가치가 있습니다. 여기서 함수형 접근 방식이 왜 적합한지 살펴보겠습니다.
Clojure is a functional language. This makes it extremely well positioned for writing large applications. As the application grows it's imperative to be able to reason about its constituent parts in isolation. Conversely, there is a lot of value in building code out of components that are testable and reusable by nature. Let's take a look at why the functional approach is such a good fit here.
함수형 언어는 글로벌 상태를 피하고 불변성을 기본값으로 선호하기 때문에 대규모 응용 프로그램을 작성하는 데 이상적입니다. 불변의 공유 상태를 가지면 애플리케이션의 일부를 분리하여 안전하게 추론할 수 있습니다. 언뜻 보기에 불변의 데이터 구조를 사용한다는 생각은 불필요하게 제한적으로 들립니다. 그러나 곧 알게 되겠지만 함수형 스타일과 관련된 많은 이점은 직접적으로 발생합니다.
Functional languages are ideal for writing large applications because they eschew global state and favor immutability as the default. Having immutable shared state allows us to safely reason about parts of the application in isolation. At first glance, the idea of using immutable data structures sounds unnecessarily restrictive. However, as we'll soon see, many of the benefits associated with the functional style stem directly from it.
가변 데이터는 값 또는 참조를 통해 전달될 수 있습니다. 안전한 접근 방식은 데이터에 대한 변경 사항이 로컬 범위에 유지되도록 보장하기 때문에 값으로 데이터를 전달하는 것입니다. 안타깝게도 이 접근 방식은 매우 비효율적이므로 대부분의 언어가 대신 참조를 통해 데이터를 전달합니다.
Mutable data can either be passed around by value or by reference. The safe approach would be to pass the data by value as it guarantees that any changes to the data will remain in the local scope. Unfortunately, this approach is extremely inefficient, so most languages pass data by reference instead.
참조로 데이터를 전달하는 것은 빠르지만 코드를 추론하기 어렵게 만듭니다. 데이터를 안전하게 사용하려면 데이터가 참조될 수 있는 모든 위치를 알아야 합니다. 애플리케이션의 크기에 따라 복잡성이 증가합니다. 코드가 데이터에 더 많이 액세스할수록 더 유명한 공을 머리 속에서 저글링해야 합니다.
Passing data by reference is fast, but it makes the code difficult to reason about. In order to safely work with the data you have to know all the places where it might be referenced. The complexity grows with the size of the application. The more code has access to a piece of data the more proverbial balls you end up having to juggle in your head.
불변의 데이터 구조는 위의 딜레마에 대한 기발한 대안을 제공합니다. 데이터 구조를 변경할 때마다 수정본이 생성됩니다. 이제 우리는 데이터를 단순하게 복사하여 제공하는 것과 동일한 보장을 받지만, 변경 사항의 크기에 비례하는 가격만 지불합니다.
Immutable data structures provide us with an ingenious alternative to the above dilemma. Every time a change is made to the data structure a revision is created. We now have the same guarantees offered by naive copying of the data, but we only pay the price proportional to the size of the change.
가비지 컬렉션을 사용하면 데이터 할당 및 할당 해제를 수동으로 추적할 필요가 없는 것처럼 불변의 데이터 구조를 사용하면 데이터 참조를 수동으로 관리할 필요가 없습니다. 사용자의 관점에서 볼 때, 우리는 언제든지 데이터를 "복사"할 수 있습니다. 이 언어는 더 이상 사용되지 않을 때 어떤 부분을 청소할 수 있는지를 파악하는 데 도움이 됩니다.
Just like garbage collection frees us from having to manually track data allocation and deallocation, immutable data structures free us from having to manage data references by hand. From the user perspective we simply "copy" the data any time we make a change. The language will take care of figuring out what parts of it can be cleaned up when they're no longer used.
이러한 데이터 구조를 갖는 것은 순수한 기능을 쉽게 작성할 수 있습니다. 순수한 함수는 단순히 부작용이 없는 함수입니다. 이러한 기능은 입력 외부의 상태에 의존하지 않으며 실행 시 외부 상태를 수정하지 않습니다. 동일한 매개 변수가 주어지면 함수는 응용 프로그램의 전체 상태에 관계없이 항상 동일한 결과를 생성합니다.
Having such data structures facilitates writing pure functions. A pure function is simply a function without side effects. These functions do not rely on any state outside their inputs and they do not modify any external state when they run. Given the same parameters, the function will always produce the same result, regardless of the global state of the application.
이러한 기능은 로컬 범위만 수정할 수 있기 때문에 분리하여 안전하게 추론할 수 있습니다. 그들은 우리에게 복잡한 행동을 만들기 위해 구성될 수 있는 자체적인 구성요소를 제공합니다. 이러한 유형의 코드를 참조 투명 코드라고 합니다.
Such functions can be safely reasoned about in isolation because we can guarantee that they're only able to modify their local scope. They provide us with self-contained components that can be composed to create complex behaviors. This type of code is referred to as being referentially transparent.
# 재사용성(Reusable)
객체 지향 언어는 구성을 위해 클래스를 사용합니다. 각 클래스의 데이터는 관련된 논리와 밀접하게 결합됩니다. 각 클래스는 특정 도메인을 나타내며 해당 도메인에 작성된 메서드는 해당 도메인 외부에서 쉽게 재사용할 수 없습니다. 기존 코드를 재사용하고자 할 때 어댑터 및 래퍼와 같은 패턴에 의존해야 하는 경우가 많습니다.
Object-oriented languages use classes for composition. The data in each class is tightly coupled to the logic associated with it. Each class represents a specific domain and the methods written in it are not easily reusable outside that domain. When we wish to reuse the existing code we often have to resort to patterns such as adapters and wrappers.
이러한 언어에서 초점은 주로 클래스를 사용하여 상태를 모델링하는 데 있습니다. 데이터는 전체 프로세스에 부수적인 것으로 간주됩니다. 기능적 프로그래밍은 데이터를 전면에 내세우고 데이터 변환의 측면에서 우리의 문제에 대해 생각하도록 장려합니다.
The focus, in such a language, is primarily on modeling the state using classes. The data is seen as being incidental to the whole process. Functional programming brings data to the forefront and it encourages us to think about our problems in terms of data transformations.
기능적 언어에서 논리와 데이터는 분리되어 있습니다. Clojure는 목록, 벡터, 맵 및 세트와 같은 일반적인 데이터 구조의 작은 집합을 제공합니다. 언어의 모든 기능은 동일한 데이터 구조에서 작동하므로 별도의 의식 없이도 결합할 수 있습니다. 이러한 접근 방식을 통해 이 기능은 재사용 가능한 핵심 구성 요소가 됩니다.
In a functional language, the logic and the data are kept separate. Clojure provides a small set of common data structures such as lists, vectors, maps, and sets. All the functions in the language operate on the same data structures allowing us to combine them without any additional ceremony. With this approach the function becomes the core reusable component.
각 함수는 데이터에 적용하려는 특정 변환을 나타냅니다. 우리가 문제를 해결해야 할 때, 우리는 그것을 일련의 변환으로 나누고 그것들을 적절한 기능에 매핑해야 합니다. 함수는 작업이 수행되는 방식을 캡처하고 구성은 수행 중인 작업을 나타냅니다. 수행되는 것과 수행되는 방법을 구분하는 코드를 선언적이라고 합니다.
Each function represents a certain transformation that we wish to apply to our data. When we need to solve a problem we simply have to break it up into a sequence of transformations and map those to the appropriate functions. The functions capture how the tasks are accomplished, while their composition states what is being accomplished. Code that separates what is being done from how it is done is referred to as being declarative.
반복을 예로 들어 보겠습니다. 명령형을 사용하여 루프를 작성하고 각 단계에서 호출되는 논리를 그 안에 넣습니다. 대조적으로, 기능적 접근법은 반복자 기능을 사용하고 반복 중에 실행하려는 논리를 매개 변수로 전달하는 것입니다.
Let's take iteration as an example. With the imperative style we would write a loop and put the logic that's invoked during each step inside it. By contrast, the functional approach is to use an iterator function and pass the logic that we want to execute during the iteration as a parameter.
반복기 함수는 한 번 작성할 수 있으며 반복, 에지 케이스 및 경계 검사에 필요한 모든 논리를 캡슐화합니다. 이제 반복해야 할 때마다 이러한 검사를 수행할 필요 없이 이 기능을 재사용할 수 있습니다.
An iterator function can be written once and it encapsulates all the logic required for iteration, edge cases, and boundary checks. We can now reuse this function without having to worry about remembering to do these checks each time we need to iterate.
# 확장성(Scalable)
불변성에 초점을 맞추면 병렬성과 동시성이라는 어려운 문제를 훨씬 쉽게 해결할 수 있습니다. 두 가지 문제를 해결하는 데 도움이 되지는 않지만, 언어는 우리가 그들에 대해 추론하는 데 큰 도움이 될 수 있습니다.
The focus on immutability makes it much easier to tackle the difficult problems of parallelism and concurrency. While there is no silver bullet for addressing either problem, the language can go a long way in helping us reason about them.
기억하시겠지만, 순수 함수는 인수에만 의존하고 범위 밖의 어떤 상태도 수정하지 않습니다. 이러한 특성을 통해 안전하게 병렬로 실행할 수 있으므로 추가 코어를 쉽게 활용할 수 있습니다.
As you'll recall, pure functions rely solely on their arguments and do not modify any state outside their scope. These properties make it possible to safely run them in parallel allowing us to easily take advantage of the extra cores.
예를 들어 컬렉션의 항목에 함수를 매핑할 수 있습니다. 우리는 지도 기능을 사용하는 것으로 시작할 수 있습니다. 이 함수는 집합 위에서 반복되고 변환기 함수를 집합 내의 각 요소에 적용합니다. 각 작업에 상당한 시간이 걸린다는 것을 알게 되면 단순히 'pmap' 기능을 사용하여 이러한 작업을 병렬로 실행하는 것으로 전환할 수 있습니다.
An example of this is mapping a function over the items in a collection. We can start by using the map
function. This function will iterate over a collection and apply a transformer function to each element inside it. Should we discover that each operation takes a significant amount of time, we can then simply switch to using the pmap
function to run these operations in parallel.
마지막으로, 불변 데이터 구조는 공유 가변 상태를 관리하는 데도 탁월한 도구인 것으로 나타났습니다. Clojure는 이러한 데이터 구조를 기반으로 STM(소프트웨어 트랜잭션 메모리) 라이브러리를 제공합니다. 트랜잭션 메모리를 사용하면 스레드를 처리할 때 수동 잠금에 대해 더 이상 걱정할 필요가 없습니다. 더 좋은 것은 새 수정본이 생성되는 동안 현재 수정본을 안전하게 읽을 수 있기 때문에 불변 데이터에 의해 지원되는 공유 상태는 쓰기 위해 잠기기만 하면 됩니다.
Finally, it turns out that the immutable data structures are also an excellent tool for managing shared mutable state. Clojure provides a Software Transactional Memory (STM) library based on these data structures. With transactional memory we no longer have to worry about manual locking when dealing with threads. Better still, shared state backed by immutable data only needs to be locked for writing since the current revision can be safely read while the new revision is being generated.