# Haskell: 뛰어난 절차적 언어

> Clean Markdown view of GeekNews topic #18812. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=18812](https://news.hada.io/topic?id=18812)
- GeekNews Markdown: [https://news.hada.io/topic/18812.md](https://news.hada.io/topic/18812.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-01-20T09:52:16+09:00
- Updated: 2025-01-20T09:52:16+09:00
- Original source: [entropicthoughts.com](https://entropicthoughts.com/haskell-procedural-programming)
- Points: 3
- Comments: 2

## Topic Body

### 사이드 이펙트를 일급 값으로 다루기  
- Haskell에서는 부수효과(예: 랜덤 숫자 생성, 출력 등)를 ‘일급 값(first class value)’처럼 취급함  
- 즉, `randomRIO(1, 6)`처럼 부수효과를 생성하는 함수 호출 자체가 결과값이 아니라, “언젠가 실행될 동작을 기술하는 객체”를 반환함  
- 이 객체는 실제로 실행될 때 랜덤 값을 만들어내지만, 그 이전에는 단순히 실행 계획만을 담고 있음  
- `IO Int` 같은 타입은 “실제로 실행되면 Int를 만들어내는 동작”을 나타내며, 호출 시점에 바로 실행되지 않고, 이후 필요한 시점에 실행됨  
- 이러한 특성으로 인해 “함수 호출 = 즉시 실행”이라는 전통적 절차적 언어와 달리, Haskell에서는 부수효과를 조합하고 나중에 실제로 실행할 수 있음  
  
### De-mystifying do blocks  
- `do` 블록은 마법적 구문이 아니라, 사실상 부수효과를 연결(bind)하고 순서대로 실행(then)하는 두 연산자로 구성됨  
  
##### then  
- `*>` 연산자는 왼쪽의 부수효과를 실행한 후 결과값을 버리고, 오른쪽 부수효과를 이어서 실행함  
- 예를 들어 `putStr "hello" *> putStrLn "world"`는 두 출력을 순서대로 합친 하나의 `IO ()` 동작을 만듦  
- `do` 블록에서 여러 줄을 쓰면 내부적으로 이런 순차 실행 연산을 이용함  
  
##### bind  
- `>>=` 연산자는 왼쪽 부수효과를 실행해서 얻은 값을 오른쪽 함수에 넘기는 역할을 함  
- 예: `randomRIO(1, 6) >>= print_side`는 주사위 결과를 `print_side`로 전달해 출력하는 부수효과를 만듦  
- `do` 블록에서 `<-` 패턴이 이 연산자를 간편하게 표현해 주는 개념임  
  
##### Two operators are all of do blocks  
- 결국 `do` 블록은 `*>`, `>>=` 이 두 연산자로 구축됨  
- 코드 가독성과 간편함 때문에 `do` 문법을 많이 쓰지만, 이보다 더 풍부한 부수효과 조합 함수들을 활용해야 Haskell의 장점을 더 살릴 수 있음  
  
### Functions that operate on side effects  
- 부수효과를 더 다양하게 다룰 수 있는 여러 함수가 표준 라이브러리에 존재함  
  
##### pure  
- `pure x`는 “아무런 추가 부수효과 없이 x 값을 결과로 만드는 동작”을 생성함  
- 예: `loaded_die = pure 4`는 항상 4를 반환하는 `IO Int`를 만듦  
  
##### fmap  
- `fmap :: (a -> b) -> IO a -> IO b` 형태로, 부수효과 결과값에 순수 함수를 적용해 새 결과값을 만드는 동작을 생성함  
- 예: `length <$> getEnv "HOME"`처럼, 환경변수를 가져오는 부수효과에 `length`를 적용해 길이를 구하는 동작을 생성할 수 있음  
  
##### liftA2, liftA3, …  
- `liftA2`, `liftA3` 같은 함수들은 여러 부수효과 결과를 하나의 순수 함수로 결합해 새 부수효과를 만듦  
- 예: `liftA2 (+) (randomRIO(1,6)) (randomRIO(1,6))`는 두 개의 주사위 값을 합산하는 부수효과를 생성함  
- `<$>`와 `<*>` 조합으로도 동일한 작업을 수행할 수 있음  
  
##### Intermission: what’s the point?  
- 이런 방식은 다른 언어에서도 가능한 단순 기능처럼 보이지만, Haskell에서는 언제든 부수효과 동작을 변수로 뽑아내거나 재조합해도 실행 시점이나 결과가 달라지지 않는 장점이 있음  
- 부수효과를 독립적으로 다룸으로써, 코드 리팩터링 시 혼동이 적고, 함수식 추론(equational reasoning)에 기반한 안전한 재사용이 가능함  
  
##### sequenceA  
- `sequenceA [IO a] -> IO [a]`는 “부수효과 동작들의 리스트”를 “리스트 결과를 내는 단일 부수효과 동작”으로 바꿔줌  
- 예: 여러 `log` 동작을 리스트로 모아두고, 나중에 `sequenceA`로 한 번에 실행시키는 식으로 활용 가능함  
- 무한 반복되는 부수효과(예: `repeat (randomRIO(1,6))`)도 리스트로 보관했다가 필요한 만큼만 `take n` 해서 `sequenceA`로 실행 가능함  
  
##### Interlude: convenience functions  
- `void`, `sequenceA_`, `replicateM`, `replicateM_` 등은 결과값을 사용하지 않거나 반복 실행할 때 편리함  
- 예: `replicateM_ 500 (putStrLn "I will not cheat again.")`처럼 반복횟수를 직접 세지 않고 부수효과를 여러 번 실행할 수 있음  
  
##### traverse  
- `traverse :: (a -> IO b) -> [a] -> IO [b]`는 리스트의 각 요소에 부수효과 함수를 적용해 결과를 리스트로 모으는 동작을 만듦  
- `sequenceA`는 사실 `traverse id`와 동일하며, `traverse_`는 결과를 버리는 버전임  
  
##### for  
- `for`는 `traverse`와 같은 기능이지만 인자를 반대로 받음  
- 예: `for numbers $ \n -> ...` 형태로 “for 루프” 같은 구문을 자연스럽게 표현함  
  
- 이런 조합 덕에 다른 언어에서 별도 문법으로 처리해야 하는 반복, 순회, 데이터 구조 변환을 Haskell에서는 라이브러리 함수 조합으로 구현할 수 있음  
  
### Leaning into the first classiness of effects  
- Haskell에서 부수효과를 일급값으로 적극 활용하면, 코드 중복 감소나 구조 개선이 가능함  
- 예를 들어, 캐시를 사용한 큰 수 소인수분해 로직에서 `IO` 대신 `State` 등을 사용해 “부수효과가 존재하지만 외부엔 영향을 주지 않는” 구조를 만들 수 있음  
- 이런 식으로 구조화된 부수효과는 필요한 부분에만 적용되며, 그 외 코드는 순수 함수로 유지할 수 있어 안전성과 유연성을 동시에 확보함  
- 최종적으로 `evalState` 등으로 실제 부수효과를 실행하고 결과를 순수한 값으로 만들 수 있음  
  
### Things you never need to care about  
- 예전 Haskell 시절부터 있었던 여러 이름(`>>`, `return`, `mapM`, 등)은 현행 함수(`*>`, `pure`, `traverse` 등)로 대체 가능함  
- 이들은 “옛날 이름 혹은 모나드 중심 설계”에서 유래했고, 요즘은 Applicative나 더 일반적 Functor 기반 접근을 권장함  
  
### Appendix A: Avoiding success and uselessness  
- “Haskell은 성공을 피한다”라는 말은 “언어가 인기나 편의성 때문에 근본 가치를 희생하지 않는다”는 의미임  
- “Haskell is useless”는 처음에 완전한 순수 함수만 허용해 정말 아무것도 못하는 언어처럼 보였지만, 이후 부수효과를 ‘일급’으로 다루는 방식이 도입되면서 실용성을 획득했다는 맥락임  
  
### Appendix B: Why fmap maps over both side effects and lists  
- `fmap`는 매우 일반적인 형태(`Functor f => (a -> b) -> f a -> f b`)로, 리스트, Maybe, IO 같은 다양한 컨테이너나 부수효과 타입에 공통적으로 적용됨  
- 리스트에 `fmap`를 적용하면 모든 원소에 함수를 씌우고, `IO`에 적용하면 결과값에 함수를 씌움  
- 이처럼 “함수를 적용할 수 있는 구조” 전반이 Functor로 불림  
  
### Appendix C: Foldable and Traversable  
- `Foldable`은 원소를 순회하며 처리할 수 있는 구조임  
- `Traversable`은 순회뿐 아니라, 새 요소로 같은 모양의 구조를 재생성할 수 있는 구조임  
- `sequenceA`나 `traverse`가 원래 구조를 유지하며 값을 모을 수 있으려면 해당 구조가 `Traversable`이어야 함  
- 트리나 Set 같은 자료구조는 구조가 값에 따라 달라질 수 있어, 순회만 가능한 경우(`Foldable`)와 실제 구조를 재생성할 수 있는 경우(`Traversable`)가 구분됨  
- 필요에 따라 리스트로 변환 후 `traverse`를 쓰는 방식 등을 통해 유연하게 부수효과를 처리할 수 있음

## Comments



### Comment 33693

- Author: bbulbum
- Created: 2025-01-21T16:28:20+09:00
- Points: 1

Reddit 보다보면 광고가 많이 뜹니다.. 근데 이름부터 약간 심리적 허들이 생겨요.  
왠지 되게 어렵고 강력한 언어 느낌..

### Comment 33609

- Author: neo
- Created: 2025-01-20T09:52:16+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=42754098) 
- Haskell의 타입 시스템은 다른 인기 있는 언어들과 비교할 때 복잡함이 있음. 특히 `*>`, `<*>`, `<*`와 같은 연산자들은 코드베이스 전반에 걸쳐 학습 곡선을 높임
  - 한 달 동안 Haskell을 사용하지 않으면 `>>=`와 `>>` 같은 연산자를 다시 공부해야 생산성을 유지할 수 있음
  - Haskell의 개념을 사람들과 대화하지 않고 혼자 공부하면 어려움이 있음

- Haskell은 명령형 프로그래밍을 개선하는 데 도움을 줌
  - 첫 번째 클래스 효과와 패턴을 사용하여 보일러플레이트 코드를 제거할 수 있음
  - 타입 안전성을 통해 상대적으로 버그 없는 코드를 빠르게 작성할 수 있음

- `traverse/mapM`의 일반화된 버전은 리스트뿐만 아니라 모든 `Traversable` 타입에 대해 작동하며 매우 유용함
  - `traverse :: Applicative f => (a -> f b) -> t a -> f (t b)` 형태로 사용 가능
  - 다른 언어에서는 비슷한 효과를 얻기 위해 많은 코드를 수동으로 작성해야 했음

- Haskell은 강력한 모나드를 가지고 있으며, 이는 Haskell을 더욱 절차적으로 만듦
  - `do` 블록에서 중간 변수를 사용할 수 있음

- Haskell로 작성된 소프트웨어로는 ImplicitCAD가 있음

- Haskell의 코드가 절차적 언어처럼 읽히지만, 부작용 함수와 함께 작업할 때의 장점을 제공함
  - IO 모나드와 함께 작업하는 것은 복잡하며, 다른 모나드 타입을 사용하고자 할 때 더욱 복잡해짐

- `>>`는 `&lt;i&gt;>`의 오래된 이름이며, 두 연산자는 왼쪽 결합 연산자임
  - `>>`는 `infixl 1`로 정의되고 `&lt;i&gt;>`는 `infixl 4`로 정의되어 있어, `&lt;i&gt;>`가 `>>`보다 더 강하게 결합됨

- Haskell의 `IO a`와 `a`는 비동기와 동기와 유사하게 느껴질 수 있음
  - 첫 번째는 대기해야 하는 약속/미래를 반환함

- 다른 언어에서는 `console.log("abc")`와 같은 함수로 간단한 IO를 수행할 수 있음
  - Haskell의 IO와 다른 점이 있는지에 대한 의문이 있음

- Haskell을 시도해보지 않은 사람들은 GHC 확장을 사용한 실제 Haskell이 너무 복잡하다고 느낄 수 있음
  - 이는 Haskell에 대한 관심을 떨어뜨릴 수 있음
