---
title: "#함수형 #프로그래밍 #개념 - #클로저"
url: "https://notes.junghanacs.com/notes/20231114T143536"
author: "Junghan Kim"
date: "2023-11-14T14:35:00+09:00"
lastmod: "2025-03-20T00:00:00+09:00"
tags: ["clojure","concepts","functional","programming"]
description: "클로저를 중심으로 함수형 프로그래밍 핵심 개념을 번역 메모와 함께 차근차근 정리해 가려는 노트."
---

> [!abstract] 이 노트에 대하여
> 
> 순수 함수와 동형성 같은 함수형 프로그래밍 개념을 클로저 맥락에서 다시 정리하려는 노트다. 아직 초안에 가깝지만, 개념을 우리말로 붙잡아 두려는 학습의 흔적이 분명하다.

## BIBLIOGRAPHY

## 관련메타

-   [#클로저](https://notes.junghanacs.com/meta/20230729T052700)

## 히스토리

-   [2022-05-21 Sat 14:35] 로그시크에 작성해 둔 것

## 동질성 : 함수형 프로그래밍 언어 클로저

[2022-05-21 Sat 14:35]

<https://en.wikipedia.org/wiki/Homoiconicity> create-at:: 2022-05-21

영어에서 번역됨-컴퓨터 프로그래밍에서 동질성은 일부 프로그래밍 언어의 속성입니다. 작성된 프로그램이 해당 언어를 사용하여 데이터로 조작 될 수 있다면 언어는 동음 이의어이므로 프로그램 자체를 읽는 것만으로 프로그램의 내부 표현을 유추 할 수 있습니다.

## 2022 함수형 프로그래밍의 개념을 다룬다.

클로저 기준으로 정리 필요 일단 비공

[Pure Function](https://notes.junghanacs.com/notes/20231114T143536#pure-function) 수학 함수는 모두 순수 함수이다. Side-Effect 이 없다. 프로그램 전체가 순수 함수로 작성되어야 이러한 효과를 누릴 수 있다.

[Persistent Data Structure](https://notes.junghanacs.com/notes/20231114T143536#persistent-data-structure) 불변하는 데이터를 유지하기 위해서는 영속 자료구조가 필요하다. 그렇다고 모든 데이터를 생성 할 때마다 복수할 수는 없다. [Structural Sharing](https://notes.junghanacs.com/notes/20231114T143536#structural-sharing) 구조 공유 기법

Laziness and Recursion

[Referential Transparency](https://notes.junghanacs.com/notes/20231114T143536#referential-transparency) 참조 투명성을 가지는 함수를 순서를 바꾸거나 지연 시킬 수 있다. 여기에 해당하는 기술이 [Memoization](https://notes.junghanacs.com/notes/20231114T143536#memoization) Automatic caching 전략이다. 참조 투명성은 [Automatic parallelization](https://notes.junghanacs.com/notes/20231114T143536#automatic-parallelization)을 가능하게 한다.

그래서 FP 의 이점은 무엇인가? --

이를 위한 개발 가이드 6 가지 이슈

[Fibonacci](https://notes.junghanacs.com/notes/20231114T143536#fibonacci)를 통해서 관련 기술을 익혀보자. 코드를 보자! 단계별로 개선해 나가는 작업.

### Simple Recursion -- [Recursion](https://notes.junghanacs.com/notes/20231114T143536#recursion)

```clojure

;; simple recursion  - bad idea
(defn stack-consuming-fibo [n]
  (cond
    (= n 0) 0
    (= n 1) 1
    :else (+ (stack-consuming-fibo (- n 1))
             (stack-consuming-fibo (- n 2)))))

(stack-consuming-fibo 9)
;(stack-consuming-fibo 1000000)
```

#### [BROKEN LINK: Recursion/Tail-Recursion] 끝 재귀 이슈가 있다.

-   letfn 사용 관련 [BROKEN LINK: \_letfn]: use local function
-   끝 재귀를 사용함에도 JVM 에서 최적화 제대로 지원 안됨
    ```clojure
    (defn tail-fibo [n]
    (letfn [(fib                   [current next n]                   (if (zero? n)                     current                     (fib next (+ current next) (dec n))))]           (fib 0N 1N n)))
    ```

#### [BROKEN LINK: Recursion/Self-Recursion] with recur [BROKEN LINK: \_recur]

-   JVM 이 꼬리 재귀를 지원하지 않으니, 클로저에서 제공하는 recur 을 사용하라. 스택을 소모하지 않음.
-   캐싱 이슈 -- 매번 같은 계산을 반복해야 하는가? 중간 값들을 캐싱하는 방법을 제공해야 된다.
-   `clojure ;; better but not great (defn recur-fibo [n] (letfn [(fib [current next n] (if (zero? n) current (recur next (+ current next) (dec n))))] (fib 0N 1N n)))`



-   [BROKEN LINK: Lazy Sequences] 지연 시퀀스 [BROKEN LINK: voclj/sequences/create/lazy-seq]
    -   이전과 다른 접근. 피보나치 몇개를 가정하고 함수를 작성할 필요가 없다. 피보나치 수열은 무한한 것이다. 그 중에서 몇개의 값을 얻을지, 몇번째 값을 얻을지는 호출하는 사람의 선택이다. 굉장히 합리적이다.
    
    -   시퀀스는 무한히 커진다. 여기서 지연 시퀀스를 이용해서 원하는 값을 얻어보자.
    
    -   [BROKEN LINK: voclj/primitives/numbers/arithmetic/rem], [BROKEN LINK: voclj/collections/lists/nth]
        ```clojure
        ;; 4) lazy-seq-fibo
        (defn lazy-seq-fibo
          ([]
           (concat [0 1] (lazy-seq-fibo 0N 1N)))
          ([a b]
           (let [n (+ a b)]
             (lazy-seq
              (cons n (lazy-seq-fibo b n))))))
        
        ;; 10개만 취하자
        (take 10 (lazy-seq-fibo))
        
        ;; rem, nth 함수. 1000000 번째 수열에서 뒤에 3자리만 뽑는 코드
        (rem (nth (lazy-seq-fibo) 1000000) 1000)
        ```
        
        -   원칙 3 의 무한대 시퀀스를 다룰 때는 지연 시퀀스 사용했으니 이제 원칙 5 시퀀스 API 를 사용하라를 따르자. iterate 를 이용하면 정말 간단하게 구현할 수 있다. -- 뭐지?! [BROKEN LINK: voclj/sequences/create/iterate]
            ```clojure
            (take 5 (iterate (fn [[a b]] [b (+ a b)]) [0 1]))
            
            (defn fibo []
              (map first (iterate (fn [[a b]] [b (+ a b)]) [0N 1N])))
            
            (take 10 (fibo))
            ;; 출력 갯수를 10개로 한정하고 싶다면...
            (set! *print-length* 10)
            (take 10000000 (fibo))
            (fibo)
            ```
        
        -   [BROKEN LINK: voclj/sequences/uses/dorun-doall-doseq-run!] 함수를 묶었다. 이것도 방법이다.
    -   [BROKEN LINK: Recursion/Mutual-Recursion] trampoline

### [BROKEN LINK: Coin Tossing] 동전 던지기 예를 통해서 Lazy 프로그래밍 기법을 더 다룬다.

왜 코인 던지기? 더 게으른 방법을 이야기하는데 무슨 이야기 인가? 언어가 제공하는 Lazy 에 다가 프로그램에 특성에 맞는 더 Lazy 한 구현 기법을 말하고자 한다. 근데 이게 다른 언어에서는 불가능한가?

[BROKEN LINK: @Functional Thinking]에서 유사한 주제가 있나? -- 훌륭한 교과서이다. 뭘 어떻게 해야하는가를 알아야 한다.

recur 을 사용한 간단한 코드. 뭐가 간단한데? 여기서 사용되는 함수들이 여러개고 recur 을 어떻게 쓰는지 생각을 해봐야 한다.

```clojure

(defn count-heads-pairs [coll]
  (loop [cnt 0 coll coll]
    (if (empty? coll)
      cnt
      (recur (if (= :h (first coll) (second coll))
               (inc cnt)
               cnt)
             (rest coll)))))

(count-heads-pairs [:h :h :h :t :h])
```

이코드에 대해서 설명해봐.

잠시만, 좋은 문서를 찾았다! 와우!

[BROKEN LINK: voclj/CONCEPT/currying_partial function]

[BROKEN LINK: voclj] [BROKEN LINK: @Clojure Cheatsheet] 개발 관련 모음을 해야 할 것 같은데?! 물론 코드를 모으는 것도 좋긴한데 하다보니 검색하는게 불편하다. 특히, 글을 적는게 아니라 활용성이 떨어진다. 그래서 Sheet 를 기준으로 정리하다보면 관련 코드가 모이지 않을까? 그렇다고 딱 띠어놓고 그 주제만 하기도 어렵다. 서로 연관이 있기 때문이다. 중복 상관 없이 해당 주제로 백링크를 걸면 되니까. 그냥 시작해도 나쁘지 않을듯하다.

왜 클로저인가? 라는 질문에 로직 프로그래밍이야기를 하는데. 궁금하다. [BROKEN LINK: Clojure/core.logic]이 그 역할을 하는데. 로직 프로그래밍이 무엇인가?

### Pure Function

[2025-03-20 Thu 15:31]

### Persistent Data Structure

[2025-03-20 Thu 15:32]

### Structural Sharing

[2025-03-20 Thu 15:32]

### Referential Transparency

[2025-03-20 Thu 15:32]

### Memoization

[2025-03-20 Thu 15:32]

### Automatic parallelization

[2025-03-20 Thu 15:33]

### Fibonacci

[2025-03-20 Thu 15:33]

### Recursion

[2025-03-20 Thu 15:33]
