Temporal.Instant
는 나노초 정밀도를 지원한다는 것 하나만으로 Date
뿐만 아니라 JavaScript 생태계의 다른 모든 대안 시간 라이브러리가 대체할 수 없는 API라고 생각한다. 표준인데다 쓰기 쉬운 API는 덤.
Lee Dogeon
@moreal@hackers.pub · 24 following · 25 followers
어느 한 개발자입니다.
GitHub
- @moreal
Hackers' Pub에 GraphQL API를 추가하고 있습니다. https://hackers.pub/graphql가 GraphQL 엔드포인트입니다. 아직 인증 기능도 없고 노출된 노드도 얼마 없습니다만, 차차 추가해 나갈 예정입니다. 다음은 예시 쿼리입니다:
{
actorByHandle(handle: "@hongminhee@hackers.pub") {
uuid
iri
type
handle
instance {
host
software
softwareVersion
}
name
bio
avatarUrl
url
}
}
Mastodon 호환 API를 구현할 계획에 대해 문의 주시는 분들이 종종 계십니다만, 아마도 Hackers' Pub은 앞으로도 Mastodon 호환 API를 구현하지는 않을 것 같습니다. 개인적으로 Mastodon 호환 API가 사용성이 많이 떨어진다고도 생각하고, 이미 Hackers' Pub 고유의 기능들 가운데 Mastodon 호환 API로 표현 불가능한 것들이 좀 있기 때문입니다.
장기적으로는 GraphQL을 이용해 웹 프런트엔드도 크게 개선하고, 모바일 앱까지 만드는 걸 염두에 두고 있습니다.
Lee Dogeon shared the below article:
논리적이 되는 두 가지 방법 - 논리와 저수준(Low-level) 자료 표현(Data representation) (2 편 중 1 편)

Ailrun (UTC-5/-4) @ailrun@hackers.pub
논리적인 말?
언제 어떤 문장이 "논리적이다"고 할 수 있을까? 일상적으로는 우리는 이를 남용해 의미가 있어 보이는 것을 "논리적이다"라고 수식하고는 한다. 그러나 필자는 이 남용이 "논리적"이라는 표현을 너무 가볍게 보고 있는 것이라 말하겠다. 어떤 것이 진정 논리적이기 위해서는 의미가 있는 것은 물론이거니와 최소한 다음의 두 조건을 더 만족해야한다.
- 몇몇 가정으로부터 그것을 증명해야 한다.
- 증명에 쓰인 체계가 모순을 보일 수 없어야 한다.
이 글에서는 이 중 1 번을 중점으로 설명하여 "좋은 가정 아래" 어떤 것이 논리적임을 증명하는 두 방법에 대해 다루어 볼 것이다. 두 방법 중 하나는 함수형 언어로 쓰인 프로그램과 유사한 구조를 가지며 나머지 하나는 일반적인 함수형 언어와는 상이한 구조를 가진다. 이 중 두번째 방법에 대해서는 (약간의 부정행위를 하면…\ldots) 2 번을 간단하게 보일 수 있기 때문에 이 또한 다룰 것이다.
논리적 증명의 대상
어떻게 어떤 것이 논리적이라는 걸 증명할 수 있는 지에 설명하기에 앞서 짚고 넘어가야 할 매우 중요한 요소가 하나 있다. 바로
질문
무엇이 논리적이냐?
는 것이다. 이 글에서는 철학적인 복잡도를 덜기 위해 이 대상을 다음과 같이 단순히 설명할 것이다.
논리적일 수 있는 대상은 명제(proposition)에 대한 판단(judgment)이다.
여기서 명제는 다음과 같이 정의되는 대상이다.
- 단위 명제(Atomic Proposition) AA는 명제이다.
- 참 명제(True Proposition) ⊤\top은 명제이다.
- 거짓 명제(False Proposition) ⊥\bot은 명제이다.
- 명제 PP와 QQ가 있을 때, P∧QP \land Q(PP 그리고 QQ)는 명제이다.
- 명제 PP와 QQ가 있을 때, P∨QP \lor Q(PP 또는 QQ)는 명제이다.
- 명제 PP와 QQ가 있을 때, P→QP \to Q(PP라면 QQ)는 명제이다.
여기서 단위 명제란 우리가 선택한, 더이상 세밀하게 분석하지 않을 논리적인 최소 단위이다. 예를 들어 1+1=21 + 1 = 2와 0+2=30 + 2 = 3을 단위 명제로 두었을 때[1] 다음과 같은 것들이 명제이다.
- ⊤\top
- 1+1=21 + 1 = 2
- (1+1=2)∧(0+2=3)(1 + 1 = 2) \land (0 + 2 = 3)
- (0+2=3)→⊥(0 + 2 = 3) \to \bot
- ⊤→⊥\top \to \bot
그렇다면 판단이란 무엇인가? 우리가 명제에 가지는 논리적 태도이다. 이 글에서는 다음과 같은 판단만을 이야기 할 것이다.
P trueP\ \texttt{true}, 즉 명제 PP가 참이다.
이를 "진리 판단"("truth judgment")이라고 한다. 좀 더 엄밀히는 다음과 같이 가정을 포함한 판단에 대해 다룰 것이다.
- Γ⊢P true\Gamma \vdash P\ \texttt{true}
이 판단은 어떤 진리 판단의 나열 Γ\Gamma을 가정했을 때 명제 PP가 참이라고 말하는 판단이다. 이를 "가정적 판단"("hypothetical judgement")이라고 한다. 또한 여기서 Γ\Gamma를 "가정"("hypothesis") 혹은 "전건"("前件", "antecedent")이라고 하고, P trueP\ \texttt{true}를 "귀결("consequent") 혹은 "후건"("後件", "succedent")이라고 한다. 요약하자면 이 절의 핵심 질문
질문
무엇이 논리적일 수 있는 대상이냐?
에 대한 답은 다음과 같다.
답
명제의 진리에 대한 가정적 판단이 논리적일 수 있는 대상이다.
진짜 논리적이 되는 방법 1 - 자연 연역(Natural Deduction)
이제 어떤 가정적 판단이 논리적인지 증명하는 한 방법에 대해서 알아보자. 우선 PP가 참이라는 판단이 가정 중 하나라면 당연히 그 가정 아래에서는 PP가 참이라고 판단할 수 있을 것이다. 이를 간략하게 표현한 규칙(rule)이
이다. 이 규칙 표현은 줄 위의 것들을 전제(premise)로 줄 밑의 결론(conclusion)을 추론(infer)해 낼 수 있음을 말한다. 전제에 쓰여있는 P true∈ΓP\ \texttt{true} \in \Gamma는 P trueP\ \texttt{true}가 가정의 나열 Γ\Gamma에 포함되어 있음을 말한다. 또한 줄 옆에 써져있는 "hyp"는 이 추론 규칙(inference rule)의 이름으로서 가정(hypothesis)을 사용한다는 것을 나타낸다.
이런 추론 규칙을 명제를 만드는 각각의 방법에 대해 정의할 수 있다. 참 명제 ⊤\top에 대해서는
라는 규칙을 줄 수 있다. 이 규칙 I⊤\text{I}\top은 어떤 Γ\Gamma를 가정하든 참 명제는 참이라고 판단할 수 있다는 규칙이다. 여기서 이름의 I\text{I}는 참 명제를 결론에 소개(Introduce)하는 규칙이라는 뜻이다. 대칭적으로 거짓 명제에 대해서는
라는 규칙을 줄 수 있다. 이 규칙 E⊥\text{E}\bot은 어떤 가정들 Γ\Gamma로 부터 거짓 명제가 참이라고 판단했다면 그 가정들 아래에서는 아무 명제나 참이라고 판단할 수 있다는 규칙이다[2]. 여기서 이름의 E\text{E}는 우리가 거짓 명제가 참임을 결론으로 가지는 전제에 도달했을 때 그 결론을 제거(Eliminate)하여 다른 결론에 도달할 수 있도록 하는 규칙이라는 뜻이다.
⊤\top과 ⊥\bot을 제외한 명제를 만드는 방법들은 이 두 종류의 규칙(I\text{I} 규칙과 E\text{E} 규칙)을 모두 가진다. P∧QP \land Q의 경우는 다음과 같다.
I∧\text{I}\land는 Γ\Gamma를 가정하여 PP와 QQ 각각이 참이라 판단했다면 같은 가정에서 P∧QP \land Q도 참이라고 판단할 수 있다는 규칙이다. 반대로 E∧\text{E}\land는 Γ\Gamma를 가정하여 P∧QP \land Q이 참이라 판단했고 Γ\Gamma에 더해 P trueP\ \text{true}와 Q trueQ\ \text{true}를 가정했을 때 RR이라는 명제가 참이라고 판단할 수 있다면 Γ\Gamma만 가정하고도 RR이 참이라고 판단할 수 있다는 뜻이다. P∨QP \lor Q와 P→QP \to Q를 위한 규칙도 나열하면 다음과 같다.
여기서 I∨1\text{I}\lor_1과 I∨2\text{I}\lor_2는 PP나 QQ 중 하나가 참이라고 판단했다면 P∨QP \lor Q가 참이라고 판단할 수 있다는 규칙이며 E∨\text{E}\lor는 P∨QP \lor Q가 참이라고 판단할 수 있고 PP가 참이라고 가정하든 QQ가 참이라고 가정하든 RR이 참이라고 판단할 수 있다면 그 가정 없이 RR이 참이라고 판단할 수 있다는 규칙이다. I→\text{I}\to는 PP가 참임을 가정해서 QQ가 참이라고 판단할 수 있다면 P→QP \to Q 또한 참이라고 판단할 수 있다는 규칙이며 E→\text{E}\to는 P→QP \to Q가 참이라고 판단할 수 있고 PP가 참이라고 판단할 수 있다면 QQ가 참이라고 판단할 수 있다는 규칙이다. 이렇게 소개 규칙과 제거 규칙의 쌍으로 논리적 증명을 하는 방식을 "자연 연역"("Natural Deduction")이라고 한다.
자연 연역으로 단순한 논리적 증명을 하는 방법의 예시를 들어보겠다. 어떤 단위 명제 AA, BB, CC에 대해서 A→BA \to B와 B→CB \to C가 참이라고 판단했다면 A→CA \to C 또한 참이라 판단할 수 있다. 이 증명은 다음과 같다. 우선 A→BA \to B 그리고 B→CB \to C와 AA가 참이라고 판단했을 때 BB가 참이라고 다음과 같이 판단할 수 있다.
이를 써서 다음과 같이 증명을 끝낼 수 있다.
따라서 논리적으로 A→BA \to B 그리고 B→CB \to C가 참임을 가정했을 때 A→CA \to C가 참이라고 판단할 수 있다.
자연 연역과 함수형 언어
혹자는 위 규칙들에서 미묘한 친숙함을 발견했을지도 모른다. 이를 좀 더 구체적으로 살펴보기 위해 →\to의 규칙들을 다시 살펴보자.
여기에 약간의 조작을 가해보자. 우선 Γ\Gamma를 단순한 판단의 나열로 취급하는 대신 이름 붙은 판단들로 취급하자. 예를 들어 위의 P trueP\ \texttt{true} 라는 판단에 xx라는 이름을 붙이면 다음과 같은 규칙을 얻는다.
더욱이 각각의 규칙들에 지금까지의 증명을 나타내는 항들을 더해보도록 하겠다. 예를 들어 I→\text{I}\to 규칙에서 Γ,x:P true⊢Q true\Gamma, x {:} P\ \texttt{true} \vdash Q\ \texttt{true}가 어떤 항 MM으로 나타내지는 증명이라면 Γ⊢P→Q true\Gamma \vdash P \to Q\ \texttt{true}의 증명은 fun (x:P)⇒M\mathtt{fun}\ (x \mathbin{:} P) \Rightarrow M이라는 항으로 나타내도록 하자. 마찬가지로 E→\text{E}\to 규칙에서 Γ⊢P→Q true\Gamma \vdash P \to Q\ \texttt{true}가 MM, Γ⊢P true\Gamma \vdash P\ \texttt{true}가 NN이라는 항으로 나타내진다면 Γ⊢Q true\Gamma \vdash Q\ \texttt{true}의 증명은 M NM\ N으로 나타내도록 하자. 이 항들을 판단에 끼워넣으면 다음과 같이 규칙을 바꿔 쓸 수 있다.
이는 (단순한) 함수형 언어의 형 검사 규칙과 동일하다!
함수 fun (x : P) => M
이 형 P -> Q
를 가지는지
검사하고 싶을 때는 x : P
를 가정한 뒤 M
이 형 Q
를
가지는지 검사하면 된다. 또한 함수 M
을 인자 N
에
적용하는 것이 형 Q
를 가지는지 알고 싶다면 함수
M
이 형 P -> Q
를 가질 때 인자 N
이 형 P
를 가지는지
검사하면 된다. 마찬가지 변화를 다음과 같이 모든 추론
규칙들에 적용할 수 있다.
⊤\top은 단위(unit) 자료형에 대응되며 ⊥\bot은 빈
자료형에 대응되고 P∧QP \land Q는 P
형과 Q
형의
쌍(pair) 자료형에 대응된다. 같은 방식으로 P∨QP \lor Q는
P
형과 Q
형의 합(sum) 자료형[3]에 대응된다.
항에서부터 각각의 추론 규칙이 (I\texttt{I} 규칙의 경우)
각 자료형의 생성자(constructor)나 (E\texttt{E} 규칙의
경우) 패턴 맞추기(pattern matching)의 형 검사 규칙에
대응된다는 것을 볼 수 있을 것이다. 이를 처음 구체화시킨
두 사람의 이름을 따 이 대응을 "커리-하워드
대응"("Curry-Howard correspondence")이라고
부른다. 이 대응이 발견됨으로써 논리적인 사고와
프로그래밍 언어의 이해 간에 중요한 연결고리가 생겼고
이는 현재에도 프로그래밍 언어의 발전과 논리학의 발전
양측에 모두 지대한 영향을 끼치고 있다.
이 대응을 따랐을 때 어떤 판단을 증명한다는 것은 그 판단의 형을 가지는 프로그램(program)을 짜는 것과 동일하다. 이를 보다 구체적으로 이해하기 위해 앞서의 예시 증명을 다시 반복해보자. 증명의 너비를 줄이기 위해 Γ\Gamma를 x:A→B true,y:B→C true,z:A truex{:}A \to B\ \texttt{true},\allowbreak y{:}B \to C\ \texttt{true}\allowbreak, z{:}A\ \texttt{true} 대신 사용하겠다.
즉 함수 x : A -> B
와 인자 z : A
가 있을 때 함수 적용 x z
이 B trueB\ \texttt{true}의 증명이다.
이어서,
따라서 fun (z : A) => y (x z)
가 x : A -> B
와 y : B -> C
가
주어졌을 때 A→C trueA \to C\ \texttt{true}의 증명이다.
지금까지 자연 연역에 대해서 알아보았다. 자연 연역은 함수형 언어를 쓰는 사람들에게는 어찌보면 매우 친숙할 수 있는 증명 방식이다. 그러나 자연 연역을 사용했을 때 거짓을 증명할 수 없다는 것을 보이는 것은 쉽지 않다. 자연 연역을 처음 도입한 게르하르트 겐첸(Gerhard Gentzen)은 같은 논문에서 거짓을 증명할 수 없다는 것을 보다 쉽게 보일 수 있는 다른 방식 또한 소개하였는데, 그것이 바로 논건 대수(論件 代數, Sequent Calculus)[4]이다.
진짜 논리적이 되는 방법 2 - 논건 대수(論件 代數, Sequent Calculus)
논건 대수 또한 명제의 진리에 대한 가정적 판단을 추론 규칙을 통해 이끌어낸다는 점은 자연 연역과 동일하다. 자연 연역과 논건 대수의 가장 큰 차이점은 명제를 만드는 각 방법에게 어떤 식으로 추론 규칙을 부여하는지에 있다. 자연 연역에서는 소개 규칙(I\text{I} 규칙)과 제거 규칙(E\text{E} 규칙)이라는 분류를 통해 ∧\land, ∨\lor, …\ldots에 추론 규칙을 부여했다. 논건 대수의 접근법은 이와는 살짝 다르다. 예를 들어 논건 대수에서의 P∧QP \land Q의 규칙에 대해 살펴보자.
차이점을 눈치챘는가? 크게 두 차이점이 있다.
- 규칙의 이름이 다르다. 😅
- E∧\text{E}\land을 대체하는 L∧\text{L}\land의 꼴이 다르다.
먼저 이름의 차이를 설명하고 넘어가자. 자연 연역의 경우 I∧\text{I}\land 규칙은 ∧\land를 소개(introduce)하는 규칙, E∧\text{E}\land 규칙은 ∧\land를 제거(eliminate)하는 규칙이었다. 반면에 논건 대수의 R∧\text{R}\land 규칙은 ∧\land를 오른쪽(Right side)에 소개하는 규칙이고 L∧\text{L}\land 규칙은 ∧\land를 왼쪽(Left side)에 소개하는 규칙이다. E∧\text{E}\land와 L∧\text{L}\land의 꼴의 차이는 이 접근법의 차이에서부터 자연스럽게 따라나온 것이다.
명제를 만드는 다른 방법들에 있어서도 논건 대수의 방식을 따라서 추론 규칙을 마련해 줄 수 있다.
그리고 논건 대수가 자연 연역과 동등함을 (비교적) 쉽게 보이기 위해서는 명제를 만드는 각 방식의 R\text{R}과 L\text{L} 규칙에 더해 다음의 두 규칙을 추가해야한다.
여기서 init\text{init} 규칙은 자연 연역에서의 hyp\text{hyp} 규칙과 같은 꼴이다. 반면에 cut\text{cut} 규칙은 직접적으로 비교할만한 자연 연역에서의 규칙이 존재하지 않는데, 이는 논건 대수가 자연 연역과 전혀 다른 방식으로 증명을 전개하기 때문이다. 자연 연역에서는 예를 들어 I∧\text{I}\land 규칙으로 만들어진 P∧QP \land Q가 참이라는 판단을 바로 E∧\text{E}\land에 사용할 수 있었다. 함수형 언어로 말하자면
case (M, N) of
(x, y) => L
같은 꼴의 사용이 가능했다. 그러나 논건 대수에서는 R∧\text{R}\land 규칙은 가정적 판단의 (오른쪽) 후건에, L∧\text{L}\land 규칙은 가정적 판단의 (왼쪽) 전건에 ∧\land를 새로 만들어낼 뿐이고 두 규칙이 상호작용할 수 있는 방법이 없다. 이 공백을 해결해주는 규칙, 즉 자연 연역의 I\texttt{I}/E\texttt{E} 규칙 쌍의 상호작용에 의한 증명을 논건 대수에서도 표현할 수 있게 해주는 규칙이 바로 cut\text{cut} 규칙이다.
그런데 이 상호작용은 과연 필요한 것인가? 위의 함수형 언어를
통한 예시에서 보다 분명히 볼 수 있는 것은 예시의 I∧\text{I}\land와
E∧\text{E}\land 규칙의 연달은 사용이 사실은 불필요하다는 점이다.
이는 자연 연역에서는 저런 식의 증명 대신 L
에서 x
가 등장하는
자리에 M
을 치환해넣고, y
가 등장하는 자리에 N
을 치환해넣은
증명 또한 가능하기 때문이다. 이런 상호작용의 불필요성에 대한
대한 자연 연역에서의 직관을 이에 대응되는 논건 대수의 cut\text{cut} 규칙에
대해서 구체화한 것이 바로 다음의
cut\text{cut} 제거 정리이다.
정리
cut\text{cut} 제거 정리
cut\text{cut}을 사용해 증명 가능한 모든 판단은
cut\text{cut}을 사용하지 않고서도 증명할 수 있다.
cut\text{cut} 제거 정리의 의의는 단순히 필요없는 규칙을 제거하는 정도에 멈추지 않는다. cut\text{cut}을 제거하고 나면 논건 대수에서 가정 없이는 거짓을 증명하지 못한다는 것이 자명해지기 때문이다.
정리
cut\text{cut}을 제거한 논건 대수의 일관성
cut\text{cut}을 제거한 논건 대수는 판단 ⊢⊥ true\vdash \bot\ \texttt{true}를 증명하지 못한다.
증명은 다음의 네 문장이면 충분하다.
- 귀결이 ⊥ true\bot\ \texttt{true}인 R\text{R} 규칙이 존재하지 않기 때문에 어떤 R\text{R} 규칙도 쓸 수 없다.
- 가정이 없기 때문에 어떤 L\text{L} 규칙도 쓸 수 없다.
- 마찬가지로 가정이 없기 때문에 init\text{init} 규칙을 쓸 수 없다.
- 따라서 어떤 규칙도 판단 ⊢⊥ true\vdash \bot\ \texttt{true}을 결론으로 주지 않는다.
앞서 이 글의 서두에서 논건 대수의 일관성을 보이기 위해 "약간의 부정행위"를 저지를 것이라고 말했다. 오해가 없도록 명시하자면 위의 증명은 cut\text{cut}을 제거한 논건 대수에 있어서는 문제가 없다. 부정행위라고 부를 만한 부분은 바로 cut\text{cut} 제거 정리의 증명이 더 어렵다는 점이다. 이 증명은 단순화하기 쉽지 않기 때문에 글에서 직접적으로 다루지는 않을 것이고, 이것이 바로 서두에서 언급한 "부정행위"이다. 다만 이 어려운 증명도 수학적 귀납법(Induction) 이상의 지식은 필요로 하지 않기 때문에, 시도해보고자 하는 독자가 있다면 직접 시도해 볼 수 있을 것이다[5].
논건 대수의 소개를 앞서의 예시 증명을 논건 대수에서 반복하는 것으로 마치도록 하자. 우선 A→BA \to B와 AA가 참이라고 판단했을 때 BB가 참이라고 다음과 같이 판단할 수 있다.
이를 써서 다음과 같이 증명을 끝낼 수 있다.
앞서의 자연 연역 증명의 구조가 크게 다름에 주의해서 논건 대수 증명의 구조를 이해해보도록 하자. 앞서의 자연 연역 증명은
의 구조를 가지고 있다. 즉 결론에 I\texttt{I} 규칙을 적용하고 어떤 가정에 E\texttt{E} 규칙을 적용해서 이 둘이 만나는 지점을 찾는 것이 증명의 구조이다. 이와는 다르게 논건 대수의 증명은
와 같이 (여러 개의) init\texttt{init} 규칙에서 시작해서 L\texttt{L} 규칙으로 가정, R\texttt{R} 규칙으로 귀결을 변경해 결론에 도달할 때까지 차례로 내려오는 구조를 가진다. 이런 구조의 차이가 바로 (자연 연역의) hyp\texttt{hyp} 규칙과 (논건 대수의) init\texttt{init} 규칙이 (같은 꼴임에도 불구하고) 다른 이름을 가지게 된 이유이자 논건 대수의 init\texttt{init} 규칙이 시작점(initial point)에 해당하는 이름을 가지게 된 이유이다.
논건 대수의 항
논건 대수를 좀 더 컴퓨터 공학적으로 이해할 수 있을까? 이를 위해 자연 연역의 경우와 마찬가지로 논건 대수에도 증명에 커리-하워드 대응을 통해 항을 주려고 시도할 수 있다. 그러나 앞서 언급한 것처럼 논건 대수 증명은 자연 연역의 증명, 그러니까 함수형 언어와 구조가 크게 다르다. 따라서 원본 그대로의 논건 대수에 주는 항은 상대적으로 복잡하고 다양한 개념들(생산자, 소비자, 소비자를 생산자로 바꾸기, 생산자와 소비자가 거래하게 하기, …\ldots)을 요구하고, 결과적으로 이는 컴퓨터 공학적인 직관성이 떨어지는 복잡한 항을 만들어 내게 된다. 이어질 2 편에서는 논건 대수에 약간의 변형을 가하면 직관적이고 쉽게 이해가 가능한 항을 줄 수 있으며, 항이 뜻하는 바가 저수준(low-level) 자료 표현(data representation)과 자연스럽게 연결될 수 있음을 설명할 것이다.
마치며
2 편의 "논리와 저수준 자료표현" 연작 중 1 편인 이 글에서는 논리적 증명을 하는 두 방법과 커리-하워드 대응에 대해서 설명했다. 1 편의 내용은 말하자면 2 편의 바탕 및 서두 역할을 하고 있다. 이 내용들이 2 편에서 다룰 본격적인 논리와 자료 표현의 관계에 대해 흥미를 불러일으켰기를 바라며 이만 마치도록 하겠다.
이 중 두번째 단위 명제는 일반적인 자연수 체계에서 거짓이나, 명제의 정의 자체는 명제가 참인지 거짓인지에 대해 논하지 않는다는 것을 강조하기 위해 단위 명제에 포함하였다. ↩︎
거짓도 참이라면 대체 무엇이 참이 아니겠는가? ↩︎
이는 Rust나 OCaml의
Result
형 혹은 Haskell의Either
형과 같은 형이다. ↩︎전건(前件, antecedent)과 후건(後件, succedent)을 개별적으로 다룰 수 있다는 의미에서 "Sequent Calculus"를 논건 대수(論件 代數)라고 번역하였다. 보다 일반적으로 "시퀀트 대수"라는 표현이 쓰이나, 이는 "시퀀트"가 의미하는 바가 "논리에서 다루는 물건/사건/…\ldots"이라는 표현보다 직관성이 떨어진다고 보아 재번역을 하였다. ↩︎
증명에 대한 구체적인 질문이 있다면 답글을 남겨주기를 바란다. ↩︎
무한 스크롤 영역 밑에 푸터 두는 서비스는 법적으로 규제해야한다 🤦♂️
Deno는 console.log()
에서 %c
형식 지정자를 통해 간단한 CSS를 사용할 수 있다.
console.log("%cHello World", "color: red");
console.log("%cHello World", "background-color: blue");
console.log("%cHello World", "text-decoration: underline");
console.log("%cHello World", "text-decoration: line-through");
console.log("%cHello World", "font-weight: bold");
console.log("%cHello World", "color: red; font-weight: bold");
console.log("%cHello %cWorld", "color: red", "color: blue");
console.log("%cHello World", "color: #FFC0CB");
console.log("%cHello World", "color: rgb(255, 192, 203)");
위 코드는 아래처럼 출력된다:
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
맥에서 VS Code의 현재 창(탭 아님)만 닫고싶을때 ⌘+⇧+W 이거 누르면 되는걸 이제 알았다;; 몰라서 맨날 마우스썼는데
어떻게 하면 React 기반 앱을 좀 더 안전하고 탄탄하게 만들 수 있을까? 우리는 그 답을 ‘리액트를 리액트답게’ 작성하는 것이라고 정의했고, react-simplikit으로 그 답을 구체화했어요. https://github.com/toss/react-simplikit
Lee Dogeon shared the below article:
Ditch the DIY Drama: Why Use Fedify Instead of Building ActivityPub from Scratch?

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub
So, you're captivated by the fediverse—the decentralized social web powered by protocols like ActivityPub. Maybe you're dreaming of building the next great federated app, a unique space connected to Mastodon, Lemmy, Pixelfed, and more. The temptation to dive deep and implement ActivityPub yourself, from the ground up, is strong. Total control, right? Understanding every byte? Sounds cool!
But hold on a sec. Before you embark on that epic quest, let's talk reality. Implementing ActivityPub correctly isn't just one task; it's like juggling several complex standards while riding a unicycle… blindfolded. It’s hard.
That's where Fedify comes in. It's a TypeScript framework designed to handle the gnarliest parts of ActivityPub development, letting you focus on what makes your app special, not reinventing the federation wheel.
This post will break down the common headaches of DIY ActivityPub implementation and show how Fedify acts as the super-powered pain reliever, starting with the very foundation of how data is represented.
Challenge #1: Data Modeling—Speaking ActivityStreams & JSON-LD Fluently
At its core, ActivityPub relies on the ActivityStreams 2.0 vocabulary to describe actions and objects, and it uses JSON-LD as the syntax to encode this vocabulary. While powerful, this combination introduces significant complexity right from the start.
First, understanding and correctly using the vast ActivityStreams vocabulary itself is a hurdle. You need to model everything—posts (Note
, Article
), profiles (Person
, Organization
), actions (Create
, Follow
, Like
, Announce
)—using the precise terms and properties defined in the specification. Manual JSON construction is tedious and prone to errors.
Second, JSON-LD, the encoding layer, has specific rules that make direct JSON manipulation surprisingly tricky:
- Missing vs. Empty Array: In JSON-LD, a property being absent is often semantically identical to it being present with an empty array. Your application logic needs to treat these cases equally when checking for values. For example, these two
Note
objects mean the same thing regarding thename
property:// No name property { "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "content": "…" }
// Equivalent to: { "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "name": [], "content": "…" }
- Single Value vs. Array: Similarly, a property holding a single value directly is often equivalent to it holding a single-element array containing that value. Your code must anticipate both representations for the same meaning, like for the
content
property here:// Single value { "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "content": "Hello" }
// Equivalent to: { "@context": "https://www.w3.org/ns/activitystreams", "type": "Note", "content": ["Hello"] }
- Object Reference vs. Embedded Object: Properties can contain either the full JSON-LD object embedded directly or just a URI string referencing that object. Your application needs to be prepared to fetch the object's data if only a URI is given (a process called dereferencing). These two
Announce
activities are semantically equivalent (assuming the URIs resolve correctly):{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Announce", // Embedded objects: "actor": { "type": "Person", "id": "http://sally.example.org/", "name": "Sally" }, "object": { "type": "Arrive", "id": "https://sally.example.com/arrive", /* ... */ } }
// Equivalent to: { "@context": "https://www.w3.org/ns/activitystreams", "type": "Announce", // URI references: "actor": "http://sally.example.org/", "object": "https://sally.example.com/arrive" }
Attempting to manually handle all these vocabulary rules and JSON-LD variations consistently across your application inevitably leads to verbose, complex, and fragile code, ripe for subtle bugs that break federation.
Fedify tackles this entire data modeling challenge with its comprehensive, type-safe Activity Vocabulary API. It provides TypeScript classes for ActivityStreams types and common extensions, giving you autocompletion and compile-time safety. Crucially, these classes internally manage all the tricky JSON-LD nuances. Fedify's property accessors present a consistent interface—non-functional properties (like tags
) always return arrays, functional properties (like content
) always return single values or null. It handles object references versus embedded objects seamlessly through dereferencing accessors (like activity.getActor()
) which automatically fetch remote objects via URI when needed—a feature known as property hydration. With Fedify, you work with a clean, predictable TypeScript API, letting the framework handle the messy details of AS vocabulary and JSON-LD encoding.
Challenge #2: Discovery & Identity—Finding Your Actors
Once you can model data, you need to make your actors discoverable. This primarily involves the WebFinger protocol (RFC 7033). You'd need to build a server endpoint at /.well-known/webfinger
capable of parsing resource queries (like acct:
URIs), validating the requested domain against your server, and responding with a precisely formatted JSON Resource Descriptor (JRD). This JRD must include specific links, like a self
link pointing to the actor's ActivityPub ID using the correct media type. Getting any part of this wrong can make your actors invisible.
Fedify simplifies this significantly. It automatically handles WebFinger requests based on the actor information you provide through its setActorDispatcher()
method. Fedify generates the correct JRD response. If you need more advanced control, like mapping user-facing handles to internal identifiers, you can easily register mapHandle()
or mapAlias()
callbacks. You focus on defining your actors; Fedify handles making them discoverable.
// Example: Define how to find actors
federation.setActorDispatcher(
"/users/{username}",
async (ctx, username) => { /* ... */ }
);
// Now GET /.well-known/webfinger?resource=acct:username@your.domain just works!
Challenge #3: Core ActivityPub Mechanics—Handling Requests and Collections
Serving actor profiles requires careful content negotiation. A request for an actor's ID needs JSON-LD for machine clients (Accept: application/activity+json
) but HTML for browsers (Accept: text/html
). Handling incoming activities at the inbox endpoint involves validating POST
requests, verifying cryptographic signatures, parsing the payload, preventing duplicates (idempotency), and routing based on activity type. Implementing collections (outbox
, followers
, etc.) with correct pagination adds another layer.
Fedify streamlines all of this. Its core request handler (via Federation.fetch()
or framework adapters like @fedify/express) manages content negotiation. You define actors with setActorDispatcher()
and web pages with your framework (Hono, Express, SvelteKit, etc.)—Fedify routes appropriately. For the inbox, setInboxListeners()
lets you define handlers per activity type (e.g., .on(Follow, ...)
), while Fedify automatically handles validation, signature verification, parsing, and idempotency checks using its KV Store. Collection implementation is simplified via dispatchers (e.g., setFollowersDispatcher()
); you provide logic to fetch a page of data, and Fedify constructs the correct Collection
or CollectionPage
with pagination.
// Define inbox handlers
federation.setInboxListeners("/inbox", "/users/{handle}/inbox")
.on(Follow, async (ctx, follow) => { /* Handle follow */ })
.on(Undo, async (ctx, undo) => { /* Handle undo */ });
// Define followers collection logic
federation.setFollowersDispatcher(
"/users/{handle}/followers",
async (ctx, handle, cursor) => { /* ... */ }
);
Challenge #4: Reliable Delivery & Asynchronous Processing—Sending Activities Robustly
Sending an activity requires more than a simple POST
. Networks fail, servers go down. You need robust failure handling and retry logic (ideally with backoff). Processing incoming activities synchronously can block your server. Efficiently broadcasting to many followers (fan-out) requires background processing and using shared inboxes where possible.
Fedify addresses reliability and scalability using its MessageQueue
abstraction. When configured (highly recommended), Context.sendActivity()
enqueues delivery tasks. Background workers handle sending with automatic retries based on configurable policies (like outboxRetryPolicy
). Fedify supports various queue backends (Deno KV, Redis, PostgreSQL, AMQP). For high-traffic fan-out, Fedify uses an optimized two-stage mechanism to distribute the load efficiently.
// Configure Fedify with a persistent queue (e.g., Deno KV)
const federation = createFederation({
queue: new DenoKvMessageQueue(/* ... */),
// ...
});
// Sending is now reliable and non-blocking
await ctx.sendActivity({ handle: "myUser" }, recipient, someActivity);
Challenge #5: Security—Avoiding Common Pitfalls
Securing an ActivityPub server is critical. You need to implement HTTP Signatures (draft-cavage-http-signatures-12) for server-to-server authentication—a complex process. You might also need Linked Data Signatures (LDS) or Object Integrity Proofs (OIP) based on FEP-8b32 for data integrity and compatibility. Managing cryptographic keys securely is essential. Lastly, fetching remote resources risks Server-Side Request Forgery (SSRF) if not validated properly.
Fedify is designed with security in mind. It automatically handles the creation and verification of HTTP Signatures, LDS, and OIP, provided you supply keys via setKeyPairsDispatcher()
. It includes key management utilities. Crucially, Fedify's default document loader includes built-in SSRF protection, blocking requests to private IPs unless explicitly allowed.
Challenge #6: Interoperability & Maintenance—Playing Nicely with Others
The fediverse is diverse. Different servers have quirks. Ensuring compatibility requires testing and adaptation. Standards evolve with new Federation Enhancement Proposals (FEPs). You also need protocols like NodeInfo to advertise server capabilities.
Fedify aims for broad interoperability and is actively maintained. It includes features like ActivityTransformer
s to smooth over implementation differences. NodeInfo support is built-in via setNodeInfoDispatcher()
.
Challenge #7: Developer Experience—Actually Building Your App
Beyond the protocol, building any server involves setup, testing, and debugging. With federation, debugging becomes harder—was the message malformed? Was the signature wrong? Is the remote server down? Is it a compatibility quirk? Good tooling is essential.
Fedify enhances the developer experience significantly. Being built with TypeScript, it offers excellent type safety and editor auto-completion. The fedify
CLI is a powerful companion designed to streamline common development tasks.
You can quickly scaffold a new project tailored to your chosen runtime and web framework using fedify init
.
For debugging interactions and verifying data, fedify lookup
is invaluable. It lets you inspect how any remote actor or object appears from the outside by performing WebFinger discovery and fetching the object's data. Fedify then displays the parsed object structure and properties directly in your terminal. For example, running:
$ fedify lookup @fedify-example@fedify-blog.deno.dev
Will first show progress messages and then output the structured representation of the actor, similar to this:
// Output of fedify lookup command (shows parsed object structure)
Person {
id: URL "https://fedify-blog.deno.dev/users/fedify-example",
name: "Fedify Example Blog",
published: 2024-03-03T13:18:11.857Z, // Simplified timestamp
summary: "This blog is powered by Fedify, a fediverse server framework.",
url: URL "https://fedify-blog.deno.dev/",
preferredUsername: "fedify-example",
publicKey: CryptographicKey {
id: URL "https://fedify-blog.deno.dev/users/fedify-example#main-key",
owner: URL "https://fedify-blog.deno.dev/users/fedify-example",
publicKey: CryptoKey { /* ... CryptoKey details ... */ }
},
// ... other properties like inbox, outbox, followers, endpoints ...
}
This allows you to easily check how data is structured or troubleshoot why an interaction might be failing by seeing the actual properties Fedify parsed.
Testing outgoing activities from your application during development is made much easier with fedify inbox
. Running the command starts a temporary local server that acts as a publicly accessible inbox, displaying key information about the temporary actor it creates for receiving messages:
$ fedify inbox
✔ The ephemeral ActivityPub server is up and running: https://<unique_id>.lhr.life/
✔ Sent follow request to @<some_test_account>@activitypub.academy.
╭───────────────┬─────────────────────────────────────────╮
│ Actor handle: │ i@<unique_id>.lhr.life │
├───────────────┼─────────────────────────────────────────┤
│ Actor URI: │ https://<unique_id>.lhr.life/i │
├───────────────┼─────────────────────────────────────────┤
│ Actor inbox: │ https://<unique_id>.lhr.life/i/inbox │
├───────────────┼─────────────────────────────────────────┤
│ Shared inbox: │ https://<unique_id>.lhr.life/inbox │
╰───────────────┴─────────────────────────────────────────╯
Web interface available at: http://localhost:8000/
You then configure your developing application to send an activity to the Actor inbox
or Shared inbox
URI provided. When an activity arrives, fedify inbox
only prints a summary table to your console indicating that a request was received:
╭────────────────┬─────────────────────────────────────╮
│ Request #: │ 2 │
├────────────────┼─────────────────────────────────────┤
│ Activity type: │ Follow │
├────────────────┼─────────────────────────────────────┤
│ HTTP request: │ POST /i/inbox │
├────────────────┼─────────────────────────────────────┤
│ HTTP response: │ 202 │
├────────────────┼─────────────────────────────────────┤
│ Details │ https://<unique_id>.lhr.life/r/2 │
╰────────────────┴─────────────────────────────────────╯
Crucially, the detailed information about the received request—including the full headers (like Signature
), the request body (the Activity JSON), and the signature verification status—is only available in the web interface provided by fedify inbox
. This web UI allows you to thoroughly inspect incoming activities during development.

When you need to test interactions with the live fediverse from your local machine beyond just sending, fedify tunnel
can securely expose your entire local development server temporarily. This suite of tools significantly eases the process of building and debugging federated applications.
Conclusion: Build Features, Not Plumbing
Implementing the ActivityPub suite of protocols from scratch is an incredibly complex and time-consuming undertaking. It involves deep dives into multiple technical specifications, cryptographic signing, security hardening, and navigating the nuances of a diverse ecosystem. While educational, it dramatically slows down the process of building the actual, unique features of your federated application.
Fedify offers a well-architected, secure, and type-safe foundation, handling the intricacies of federation for you—data modeling, discovery, core mechanics, delivery, security, and interoperability. It lets you focus on your application's unique value and user experience. Stop wrestling with low-level protocol details and start building your vision for the fediverse faster and more reliably. Give Fedify a try!
Getting started is straightforward. First, install the Fedify CLI using your preferred method. Once installed, create a new project template by running fedify init your-project-name
.
Check out the Fedify tutorials and Fedify manual to learn more. Happy federating!
Hackers' Pub은 검색에서 몇 가지 기본적인 문법들을 지원하고 있습니다. 문서화되지 않았기 때문에 한 번 정리해 봅니다.
문법 | 설명 | 예시 |
---|---|---|
" 키워드 " |
따옴표 안에 들어간 문자열을 띄어쓰기를 포함하여 찾습니다. 대소문자는 구분하지 않습니다. (따옴표 안에 따옴표를 넣으려면 \" 와 같이 이스케이프.) |
"Hackers' Pub" |
from: 핸들 |
해당 사용자가 쓴 콘텐츠만 추립니다. | from:hongminhee from:hongminhee@hollo.social |
lang: ISO 639-1 |
해당 언어로 쓰여진 콘텐츠만 추립니다. | lang:ko |
# 태그 |
해당 태그가 달린 콘텐츠만 추립니다. 대소문자는 구분하지 않습니다. |
#Fedify |
조건 조건 |
띄어쓰기 양 옆의 조건을 모두 만족하는 콘텐츠만 추립니다(논리곱). | "Hackers' Pub" lang:ko |
조건 OR 조건 |
OR 연산자 양 옆의 조건 중 하나라도 만족하는 콘텐츠를 추립니다(논리합). |
해커즈퍼브 OR "Hackers' Pub" lang:ko |
( 조건 ) |
괄호 안의 연산자들을 먼저 결합합니다. | (해커즈퍼브 OR 해커즈펍 OR 해커스펍) lang:ko |
구체적인 동작 방식은 Hackers' Pub 소스 코드를 보시면 더 자세히 알 수 있습니다.
개인적으로는 k8s쓰는 가장 큰 이유는 개발자 복지라고 생각한다. 적정기술만 쓰면 대부분의 사람들은 뭔가를 실 서비스에서 경험할 기회를 잃어버린다. 아니 이건 됐고…
온프레미스 클러스터 오퍼레이션 부담이나 EKS같은 서비스의 사용료 걱정만 없다면 쓰는게 무조건 낫다고 생각한다.
일단 k8s뿐만 아니라 컨테이너/머신 오케스트레이션의 세계에서 앱과 머신은 좀 더 잘 죽어도되는 존재가 된다. (물론 stateful한 호스트와 앱을 최대한 stateless하게 하거나, 상태를 분리하여 격리시켜야 하긴 한다)
그러면 docker-compose로 충분하지 않느냐 말할 사람도 있겠지만 처음에야 docker-compose 쓰는거나 k8s 쓰는거나 그게 그거지만(오히려 k8s가 성가실것이다) 마이그레이션의 때가 오면 난 그걸 감당할 자신이 없다.
물론 자신만의 가볍고 쏙 맘에드는 솔루션을 고집할 사람도 있을텐데… 난 남들이 다 쓰는거 쓰는게 편하다.
2025年 오픈소스 컨트리뷰션 아카데미 參與型 멘토團 募集 公告가 떴다. Fedify 프로젝트의 메인테이너로서 멘토團에 志願하고자 한다. 志願書가 .hwp 파일이기에 큰 맘 먹고 한컴오피스 한글 for Mac도 購入했다. (아무래도 앞으로 .hwp 파일 다룰 일이 많을 것 같다는 豫感이 들어서…)
좀 쓰다 급하게 마무리하고 자러 갑니다. Hacker's Pub 가입 이후 처음이라 올리긴 하는데요…
옥텟 규칙으로 본 IETF RFC 9110 “HTTP Semantics” https://eonj.github.io/trouble.log/2025-04-09.an-octet-aspect-to-ietf-rfc-9110/
에모지 반응 기능이 배포되었습니다. 당분간 버그가 좀 있을 수도 있지만 양해 바랍니다. 전 이제 차단을 구현하러 가겠습니다…
Lee Dogeon shared the below article:
같은 것을 알아내는 방법

Ailrun (UTC-5/-4) @ailrun@hackers.pub
같은 것과 같지 않은 것
국밥 두 그릇의 가격이 얼마인가? KTX의 속력이 몇 km/h인가? 내일 기온은 몇 도인가? 일상에서 묻는 이런 질문은 항상 같음의 개념을 암시적으로 사용하고 있다. 앞의 예시를 보다 명시적으로 바꾼다면 아래와 같이 (다소 어색하게) 말할 수 있다.
- 국밥 두 그릇의 가격은 몇 원과 같은가?
- KTX의 속력은 몇 km/h와 같은가?
- 내일 기온은 몇 도와 같은가?
이런 질문들의 추상화인 이론들은 자연스럽게 언제 무엇과 무엇이 같은지에 대해서 답하는 데에 초점을 맞추게 된다. 예를 들면
- x2+x+1=0x^2 + x + 1 = 0의 실수 해의 갯수는 0과 같다.
- 물 분자 내의 수소-산소 연결 사이의 각도는 104.5도와 같다.
- 합병 정렬의 시간 복잡도는 O(nlogn)O(n\log{n})과 같다.
등이 있다. 이렇게 어떤 두 대상이 같은지에 대해서 이야기를 하다보면 반대로 어떤 두 대상이 같지 않은지에 대해서도 이야기하게 된다. 즉,
- x+4x + 4를 22로 나눈 나머지는 x+1x + 1을 22로 나눈 나머지와 같지 않다.
- 연결 리스트(Linked List)와 배열(Array)은 같지 않다.
- 함수 λ x→x\lambda\ x \to x와 정수 55는 같지 않다.
같은 것과 판정 문제(Decision Problem)
이제 컴퓨터 과학(Computer Science)과 프로그래밍(Programming)에 있어 자연스러운 의문은 "두 대상이 같은지 아닌지와 같은 답을 주는 알고리즘(Algorithm)이 있나?"일 것이다. 다시 말해서 두 대상 aa와 bb를 입력으로 주었을 때
- 알고리즘이 참 값(True\mathtt{True})을 준다면 aa와 bb가 같고
- 알고리즘이 거짓 값(False\mathtt{False})을 준다면 aa와 bb가 같지 않은
알고리즘이 있는지 물어볼 수 있다. 이런 어떤 명제가 참인지 거짓인지 판정하는 알고리즘의 존재 여부에 대한 질문을 "판정 문제"("Decision Problem")라고 하며, 명제 PP에 대한 판정 문제에서 설명하는 알고리즘이 존재한다면 "PP는 판정 가능하다"("PP is decidable")고 한다. 즉, 앞의 질문은 "임의의 aa와 bb에 대해 aa와 bb가 같은지 판정 가능한가?"라는 질문과 같은 의미라고 할 수 있다.
이 질문에 대한 대답은 당연하게도 어떤 대상을 어떻게 비교하는지에 따라 달라진다. 예를 들어 우리가 32 비트(bit) 정수에 대해서만 이야기하고 있다면 "임의의 32 비트 정수 aa와 bb에 대해 aa와 bb가 각 비트별로 같은지 판정 가능한가?"라는 질문에 대한 답은 "그렇다"이다. 반면 우리가 비슷한 질문을 자연수를 받아 자연수를 내놓는 임의의 함수에 대해 던진다면 답은 "아니다"가 된다.[1]
그렇다면 어떤 대상의 어떤 비교에 대해 판정 문제를 물어보아야할까? 프로그래머(Programmer)로서 명백한 대답은 두 프로그램(Program)이 실행 결과에 있어서 같은지 보는 것일 것이다. 그러나 앞서 자연수를 받아 자연수를 내놓는 함수에 대해 말했던 것과 비슷하게 두 프로그램의 실행 결과를 완벽하게 비교하는 알고리즘은 존재하지않는다. 이는 우리가 두 프로그램의 같음을 판정하고 싶다면 그 같음을 비교하는 방법에 제약을 두어야 함을 말한다. 여기서는 다음의 두 제약을 대표로 설명할 것이다.
- 문법적 비교(Syntactic Comparison)
- β\beta 동등성 (β\beta Equivalence)
1. 문법적 비교(Syntactic Comparison)
이 방법은 말 그대로 두 프로그램이 문법 수준에서 같은지를 보는 것이다. 예를 들어 다음의 두 JavaScript 프로그램은 문법적으로 같은 프로그램이다.
// 1번 프로그램
let x = 5;
console.log(x);
// 2번 프로그램
let x = 5;
console.log( x );
공백문자의 사용에서 차이가 있으나, 그 외의 문법 요소는 모두 동일함에 유의하자. 반면 다음의 두 JavaScript 프로그램은 동일한 행동을 하지만 문법적으로는 다른 프로그램이다.
// 1번 프로그램
let x = 5;
console.log(x);
// 2번 프로그램
let x = 3 + 2;
console.log(x);
두 프로그램 모두 x
에 5
라는 값을 할당하고 5
를 콘솔에 출력하나, 첫번째 프로그램은 = 5;
를, 두번째 프로그램은 = 3 + 2
을 사용하여 5
를 할당하고 있기 때문에 문법적으로 다르다.
문법적 비교는 이렇게 문법만 보고서 쉽게 판정할 수 있다는 장점이 있으나, 두번째 예시처럼 쉽게 같은 행동을 함을 이해할 수 있는 프로그램에 대해서도 "같지 않음"이라는 결과를 준다는 단점을 가진다. 혹자는
3 + 2
같은 계산은 그냥 한 다음에 비교하면 안돼? 컴파일러(Compiler)도 상수 전파(Constant Propagation) 최적화라던지로3 + 2
를5
로 바꾸잖아?
라는 생각을 할 수도 있을 것이다. 이 제안을 반영한 방법이 바로 β\beta 동등성이다.
2. β\beta 동등성
바로 앞의 소절에서 단순 계산의 추가에 의해 같음이 같지 않음으로 변하는 것을 보았다. 이런 상황을 피하기 위해서는 같음을 평가할 때 프로그램의 실행을 고려하도록 만들어야 한다. 가장 대표적인, 대부분의 프로그래밍 언어(Programming Language)에 존재하는 프로그램의 실행은 함수 호출이다. 따라서 함수 호출을 고려한 같음의 비교는 f(c)
와 함수 f
의 몸체 b
안에서 인자 x
를 c
로 치환한 것을 같다고 취급해야한다. 예를 들어
let f = (x) => x + 3;
이 있다면, f(5)
와 5 + 3
혹은 8
을 같은 프로그램으로 취급해야한다. 이 비교 방법의 큰 문제는 함수가 종료하는지 알지 못한다는 것이다. 두 프로그램 a
와 b
를 비교하는데, a
가 종료하지 않는 함수 l
을 호출한다면, 이 알고리즘은 "같음"이나 "같지 않음"이라는 결과를 낼 수조차 없다. 즉, 올바른 판정법이 될 수 없다.
더 심각한 문제는 아직 값을 모르는 변수가 있는 "열린 프로그램"("Open Program")에 대해서도 이런 계산을 고려해야한다는 것이다. 다음의 JavaScript 예시를 보자.
let g = (x) => f(x) + 3;
let h = (x) => (x + 3) + 3;
g
와 h
는 같은 프로그램일까? 우리가 g
와 h
가 같은 프로그램이기를 원한다면 f(x)
와 x + 3
을 같은 프로그램으로 보아야한다. 대부분의 프로그램은 함수 안에서 쓰여지기 때문에 프로그램의 비교는 거의 항상 g
와 h
의 몸체와 같은 열린 프로그램들의 비교이다. 따라서 g
와 h
를 다른 프로그램으로 본다면 계산을 실행하여 두 프로그램을 비교하는 의미가 퇴색되고 만다. 그렇기 때문에 우리는 x
와 같이 값이 정해지지 않은 변수가 있을 때에도 f(x)
을 호출하여 비교해야만 한다. 이는 우리가 단순히 모든 함수가 종료하는지 여부를 떠나서, 함수의 몸체에 등장하는 모든 부속 프로그램(Sub-program)이 종료하는지 아닌지를 따져야만 한다는 이야기이다.
이런 강한 제약조건으로 인해 β\beta 동등성을 통해서 프로그램 비교의 판정 문제를 해결 가능한 곳은 매우 제한적이지만, β\beta 동등성이 매우 유용한 한가지 경우가 있다. 바로 의존 형이론(Dependent Type Theory)의 형검사(Type Checking)이다.
의존 형이론과 형의 같음
의존 형이론은 형(Type)에 임의의 프로그램을 포함할 수 있도록 하는 형이론(Type Theory)의 한 종류이다. 예를 들어 명시적인 길이(n
)를 포함한 벡터(Vector) 형을 Vector n Int
과 같이 쓸 수 있다. 이 형은 n
개의 Int
값을 가진 벡터를 표현하는 형이다. 이제 append
라는 두 벡터를 하나로 연결하는 함수를 만든다고 해보자. 대략 다음과 같은 형을 쓸 수 있을 것이다.
append : Vector n a -> Vector m a -> Vector (n + m) a
즉, append
는 길이 n
짜리 a
형의 벡터와 길이 m
짜리 a
형의 벡터를 합쳐서 길이 n + m
짜리 a
형의 벡터를 만드는 함수이다. 이 함수를 사용해서 길이 5
의 벡터를 길이 2
와 길이 3
짜리 벡터 x
, y
로부터 만들고 싶다고 하자.
append x y : Vector (2 + 3) a
안타깝게도 우리는 길이 2 + 3
짜리 벡터를 얻었지, 길이 5
짜리 벡터를 얻진 못했다. 여기서 앞서의 질문이 다시 돌아온다.
아니,
2 + 3
를5
로 계산하면 되잖아?"
그렇다. 이런 의존 형에 β\beta 동등성을 적용하면 우리가 원하는 형을 바로 얻어낼 수 있다. Vector (2 + 3) a
과 Vector 5 a
는 같은 형이기 때문이다. 더욱이, 의존 형의 경우 종료하지 않는 부속 프로그램이 잘못된 형을 줄 수 있기 때문에 많은 경우 종료하지 않는 부속 프로그램을 어차피 포함하지 않는다. 다시 말해, 앞서 말한 제약 조건 즉 모든 부속 프로그램이 종료해야만 한다는 제약조건은 의존 형의 경우 상대적으로 훨씬 덜 심각한 제약조건이 되는 것이다.
이런 의존 형에 있어서의 β\beta 동등성 검사를 "변환 검사"("Conversion Check")라고 하며, 두 형이 β\beta 동등일 경우 이 두 형이 서로 "변환 가능하다"("Convertible")라고 한다. 이 변환 검사는 의존 형이론 구현에 있어서 가장 핵심인 기능 중 하나이며, 가장 잦은 버그를 부르는 기능 중 하나이기도 하다.
마치며
이 글에서는 같음과 같지 않음의 판정 문제에 대해 간략히 설명하고 프로그램의 같음을 판정하는 법에 대해서 단순화하여 다루어보았다. 구체적으로는 문법 기반의 비교와 β\beta 동등성을 통한 비교로 프로그램의 같음을 판정하는 법을 알아보았고, 이 중 β\beta 동등성이 적용되는 가장 중요한 예시인 의존 형이론을 β\beta 동등성을 중점으로 짤막하게 설명하였다. 마지막 문단에서 언급했듯 의존 형이론의 구현에 있어서 β\beta 동등성을 올바르게 구현하는 것은 가장 중요한 작업 중 하나이기에, 최근 연구들은 β\beta 동등성의 구현 자체를 의존 형이론 안에서 함으로서 검증된 β\beta 동등성의 구현을 하기 시작하고 있다. 이 글이 같음과 같지 않음과 판정 문제 그리고 β\beta 동등성에 있어 유용한 설명을 내놓았기를 바라며 이만 줄이도록 하겠다.
두 함수가 같다라고 보는 방법에 따라 다르나, 두 함수가 항상 같은 값을 가진다면 같다고 하자. 이때 함수의 판정 문제는 정지 문제(Halting Problem)와 동일하다. 임의의 튜링 기계(Turing Machine) ff가 입력 nn을 받았을 때 종료하면 g(n)=1g(n) = 1, 아니면 g(n)=0g(n) = 0이라고 하면 이 함수 gg와 상수 함수 c(n)=1c(n) = 1가 같은 함수임을 보이는 것은 ff가 항상 종료한다는 것을 보이는 것과 동등하다. ↩︎
해키지(Hackage)[1]에 패키지를 업로드하면 자동으로 빌드, 문서 생성, 테스트가 진행된다. 그런데 이게 시간이 좀 걸린다.(체감상 10분 정도) 이 과정이 자동으로 완료되기 전에 참지 못하고 수동으로 문서를 업로드하면 자동으로 진행되던 것들이 모두 중단된다. https://github.com/haskell/hackage-server/issues/1376
하스켈 패키지 저장소 ↩︎
이제 정말로 좋아요·에모지 반응 기능을 구현할 때가 왔다… 하지만 일단 낼모레 있을 第8回FediLUG勉強会 발표 준비부터 해야 한다…
알림이 생겨서 보니, 놓친 답글들이 좀 있다는걸 깨달을 수 있었다 😂
@morealLee Dogeon 이젠 더더욱 SNS 같이 쓸 수 있는...!!
Hackers' Pub에 이제 알림 기능이 생겼습니다. 우상단 프로필 사진 바로 왼쪽에 알림 아이콘이 추가되었고, 이제 읽지 않은 알림이 있을 경우 그 위에 빨간 동그라미가 표시됩니다. 알림의 종류는 현재 다음 다섯 가지입니다:
- 누가 날 팔로했을 때
- 누가 날 언급했을 때
- 누가 내 글에 답글을 달았을 때
- 누가 내 글을 인용했을 때
- 누가 내 글을 공유했을 때
@curry박준규
@bglbgl gwyng 알림이 달리 없어서 지나쳐버렸네요, 의견 감사합니다! 혹시 이야기 해주신 UTop이 아래 링크의 UTop이 맞다면, 저 UTop을 직접 구현해보는 걸 추천해주신걸까요?
https://opam.ocaml.org/blog/about-utop/
@morealLee Dogeon
@curry박준규 저는 OCaml을 많이 안 써봤지만 그걸로 만들기 유리한 종류의 프로그램이 특별히 있는지 잘모르겠네요. 아마 이미 아시겠지만, ReScript같은건 바로 써먹고 생산성도 잘나와서 장점을 쉽게 느낄수 있을거 같네요.
Lee Dogeon shared the below article:
셸 언어는 때로 추하길 요구 받는다

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub
명령줄 인터페이스(CLI)는 컴퓨터와 상호작용하는 가장 오래된 방식 중 하나다. 그리고 이 인터페이스를 지배하는 것은 셸 언어다. 그런데 흥미로운 점은 셸 언어가 일반적인 프로그래밍 언어들과는 상당히 다른 설계 철학을 따른다는 것이다. 한 마디로 요약하자면, 셸 언어는 때로 “추함”을 받아들여야 한다.
간결함의 미학
Bash나 zsh와 같은 전통적인 셸을 보자. grep -r "error" /var/log | wc -l
와 같은 명령은 암호처럼 보일 수 있지만, 타이핑하는 데 몇 초밖에 걸리지 않는다. 이러한 간결함은 우연히 생긴 것이 아니다. 셸 환경에서는 사용자가 빠르게 입력하고, 결과를 확인하고, 다시 명령을 수정하는 반복적인 워크플로우가 일반적이다. 여기서 핵심은 “대화형” 경험이다.
PowerShell의 딜레마
PowerShell은 마이크로소프트가 셸의 개념을 재정의하려 한 야심찬 시도였다. 객체 지향적 파이프라인, 일관된 동사–명사 구문, 그리고 자세한 매개변수 이름 등은 모두 코드의 가독성과 유지보수성을 높이기 위한 설계였다.
그러나 다음 명령을 비교해보자:
Bash:
find . -name "*.log" -mtime -7 \
| xargs grep "error" \
| sort \
| uniq -c
PowerShell:
Get-ChildItem -Path . -Filter *.log `
| Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays(-7)} `
| ForEach-Object {Select-String -Path $_.FullName -Pattern "error"} `
| Sort-Object `
| Group-Object `
| Select-Object Name,Count
PowerShell의 명령은 더 명확하고 자기 설명적이지만, 대화형 셸에서 빠르게 실험하고 반복하기에는 너무 장황하다. PowerShell 설계자들은 “추함”을 견디지 못하고 너무 많은 “다림질”을 해버린 것이다.
균형점 찾기
흥미롭게도 최근의 Nushell 같은 현대적인 셸은 이 교훈을 받아들이고 있다. 구조화된 데이터 처리와 같은 PowerShell의 장점을 가져오면서도, 대화형 사용에 필요한 간결함을 유지하려 노력한다.
셸 언어의 진정한 성공은 “아름다운 코드”와 “효율적인 상호작용” 사이의 균형에 달려 있다. 이는 때로 완벽한 문법이나 일관성보다는 실용적인 “추함”을 수용해야 함을 의미한다.
결론
프로그래밍 언어의 세계에서는 우아함과 일관성이 미덕이다. 그러나 셸의 세계에서는 타이핑 효율성, 속도, 그리고 대화형 적합성이 우선시된다. 이것이 바로 셸 언어가 때로 “추함”을 요구받는 이유다. PowerShell의 제한적인 성공은 이 기본적인 진실을 간과한 데서 비롯된 것일지도 모른다.
그리고 어쩌면 이것은 소프트웨어 설계 전반에 걸친 더 깊은 교훈을 담고 있다: 모든 도구는 그 사용 맥락에 맞게 설계되어야 한다는 것이다. 셸 언어에서는 그 맥락이 바로 키보드와 사용자 사이의 빠른 대화다.
인용한 글의 내용과는 상관 없는 이야기인데, 현재는 단문에서는 단문이든 게시글이든 인용할 수 있는 반면, 게시글에서는 단문도 게시글도 인용을 못 하게 되어 있다. 별 생각을 안 하고 그렇게 만든 거긴 한데, 잘 생각해 보니 오히려 인용 기능은 게시글에서 더 유용할 것 같다.
하루 빨리 게시글에서도 인용이 가능하게 개선을 하도록 하겠습니다…
@parksbSimon Park 헉 어떻게 하셨나요
@kodingwarriorJaeyeol Lee 심플하게 가장 가까운 .vim/config.lua 파일을 찾아서 해당 파일에 명시된 린터와 포매터 정보를 읽도록 만들었어요. 급하게 필요해서 만든거라 엉성해요 ㅎㅎ https://github.com/parksb/dotfiles/commit/ca3bc66b03a9c2ed2dc7388bedc78fe9d62dbb08
Hackers' Pub 에서 좋아요 느낌을 표현하고 싶을 때
- 공유 혹은 댓글을 다는 방법이 있겠고
- 그냥 지나치기는 아쉬워서
- (우선은) 공유를 하고 있기는 한데,
잘하고 있는건가 싶은 생각이 들 때도 있다.
개인적으로 공유는 팔로워들에게 공유하고 싶을 때 쓰고 싶은 기능인데… 최근에 다소 남발하게 된다.[1]
서로 멘션 주고 받다가, 답글 마지막에 좋아요 하트 느낌으로 마무리 짓고 싶은 마음 뿜뿜할 때에도, 그냥 아무말 안하고 마무리 하기도 하고. (이모티콘 댓글 정도를 남긴다거나 하는 방법은 있음)
@hongminhee洪 民憙 (Hong Minhee) 님이 이모티콘 좋아요 기능을 고민중이라 하시니, 그때까지는 좀 더 공유 기능을 남발해 보는 걸로. 😂
결론 : 이 글은 무차별 공유에 대한 자기 합리화를 위한 글이었던 것입니다. 😅
좋아요 느낌의 표현으로도 병행해서 사용하고 있으므로 ↩︎
많은 분들이 인용 방법을 혼란스러워 하셔서, 인용 버튼을 추가했습니다. 게시글이나 단문 아래의 아이콘들 중에 왼쪽에서 세 번째 아이콘을 누르시면 해당 콘텐츠를 인용한 글들이 나열되고, 그 위에 인용 글 입력란이 뜨게 됩니다. 거기서 인용 글을 쓸 수 있습니다. 아, 종래의 인용 UI도 그대로 사용하실 수 있습니다.
참고로 인용 아이콘은 @xtjuxtapose 님께서 수고해 주셨습니다. 감사합니다.
RE: https://hackers.pub/@xt/0195eb06-9f50-763d-85c8-5600ec78c539
https://elixir-lang.org/blog/2025/03/25/cyanview-elixir-case
수퍼볼 같은데서 수백대의 방송장비를 Elixir를 통해서 제어하고, Phoenix LiveView로 시각화하는 사례. Elixir 생태계에 Nerves라는 임베디드 시스템 제어 프레임워크가 있었던걸로 기억하고 있는데, 이게 이렇게 이어지는군아
Polymarket 등의 예측 시장에는 오라클 문제가 있다. 블록체인으로 만들어봤자, 어차피 베팅의 승패를 결정하려면 외부에서 딸깍 해줘야한다. 가령 4월 내에 탄핵이 이뤄질거냐 마냐 같은 게임을 상상하면 된다. 그 딸각하는 사람을 어떻게 믿을수 있냐는 문제가 오라클 문제다.
오라클 문제가 없는 예측 시장이 하나 생각났는데, 바로 수학 문제가 언제 풀릴 것이냐에 대한 것이다. 가령 리만 가설이 앞으로 1,000,000 블록 내에 풀릴지, 또는 P=NP랑 둘 중에 뭐가 먼저 풀릴지 등에 대한 것이다. 여기서 풀리는건 Lean 등으로 작성된 Formal Proof을 통해서 온체인으로 판단한다.
수학자들은 자신이 베팅을 걸어놓고 연구를 열심히해서 돈을 벌 수도 있다. 또 직접 연구를 하지 않더라도 GPU를 사서 자신의 베팅에 유리하도록 연구에 도움을 줄 수 있다. 앞서 그냥 유명하단 이유로 너무 거창한 문제를 예시로 들었는데, 그보다는 더 작고 쉬운 많은 문제들에 대해 이런 식의 경제가 돌아가는걸 상상해보자. 연구에 들어가는 자원 배분이 최적화되지 않을까?
@bglbgl gwyng 개인적으로는 오라클이 필수적인 애플리케이션은 처음부터 블록체인으로 만들면 안 된다고 생각합니다. 😂 관련된 주제로 〈탈중앙 게임, 그리고 블록체인과 NFT〉라는 글을 예전에 쓴 바 있습니다.
여태까지 Heroicons만 써 왔는데, Opensource Svg Icons라는 프로젝트가 있다는 걸 알게 되었다. UI 만들 때 아주 유용한 듯!
RE: https://hackers.pub/@hongminhee/0195e9f1-18b1-7844-b7c4-6647245a8260
洪 民憙 (Hong Minhee) replied to the below article:
Fedify CLI로 Content Warnings 이해하기
Lee Dogeon @moreal@hackers.pub
Warning
제목이 적절한지 잘 모르겠다. 본문은 Mastodon에 있는 Content Warnings이라는 것이 ActivityPub Activity 객체에서 어떻게 묘사되는지 확인하는 내용이다. 정확한 내용이 아닐 수 있다.
서문 (동기)
Mastodon에서 글을 쓸 때 Content Warnings을 자주 쓰는데:
- 내가 쓰는 글이 어떤 사람에게는 기분 상할 글일 수도 있을까 하는 걱정도 있고,
- 혼자 말을 자주 적는데 소음같이 느껴져서 보고 싶지 않을 사람도 있을까 싶어서 "혼자 말" 같은 경고문을 달고 적어놓는다.
요즘은 "혼자 말" 대신 요약을 좀 적어놓는 편인 것 같다. 그런데 Mastodon에서 글을 적으면 몇 글자 더 적을 수 있는지, 글자 수 제한을 표시해준다. Content Warnings을 적는데도 글자 수 제한이 줄어드는 것을 보고 본문과 Content Warnings가 같은 필드에 있는 걸까 그런 궁금증이 들었다. 어떻게 생겼는지 보고 글을 적고 있는 지금 다시 생각하면, 조금 잘못된(?) 상상이었던 것 같지만 암튼 그랬다.
본문
Activity 객체 읽어오기
내가 적은 글의 Activity 객체 버전을 확인해보려면 어떻게 해야 하지 싶던 중, @hongminhee 님이 만드신 Fedify에서 제공하는 CLI 도구에 관련 기능이 있었던 것 같아 살펴보니 fedify lookup
이라는 명령어가 있었다.
사용법은 아래와 같이 인자로 글 URL을 넘겨주면 됐다.
fedify lookup https://social.silicon.moe/@moreal/114252336335817713
그러면 아래와 같이 Activity 객체 내용을 보여준다:
$ fedify lookup https://social.silicon.moe/@moreal/114252336335817713
✔ Fetched object: https://social.silicon.moe/@moreal/114252336335817713.
Note {
id: URL "https://social.silicon.moe/users/moreal/statuses/114252336335817713",
attribution: URL "https://social.silicon.moe/users/moreal",
contents: [ "<p>본문</p>", <ko> "<p>본문</p>" ],
published: 2025-03-30T16:31:40Z,
replies: Collection {
id: URL "https://social.silicon.moe/users/moreal/statuses/114252336335817713/replies",
first: CollectionPage {
partOf: URL "https://social.silicon.moe/users/moreal/statuses/114252336335817713/replies",
next: URL "https://social.silicon.moe/users/moreal/statuses/114252336335817713/replies?only_other_accounts=true&page=true"
}
},
shares: Collection {
id: URL "https://social.silicon.moe/users/moreal/statuses/114252336335817713/shares",
totalItems: 0
},
likes: Collection {
id: URL "https://social.silicon.moe/users/moreal/statuses/114252336335817713/likes",
totalItems: 0
},
summary: "Content Warning 테스트",
url: URL "https://social.silicon.moe/@moreal/114252336335817713",
to: URL "https://social.silicon.moe/users/moreal/followers",
cc: URL "https://www.w3.org/ns/activitystreams#Public",
sensitive: true
}
✔ Successfully fetched the object.
Activity 객체 이해하기
"Content Warnings"에 넣었던 Content Warning 테스트
라는 문구는 summary
필드에 들어있었다.
summary
필드에 대해 살펴보기 위해서 ActivityPub 문서에 들어갔다. "Note"
를 키워드로 검색해보니 아래 같은 예제를 발견했다:
{"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"to": ["https://chatty.example/ben/"],
"attributedTo": "https://social.example/alyssa/",
"content": "Say, did you finish reading that book I lent you?"}
예전에 Fedify에 기여할 때 기억으로는 Activity가 JSON-LD 포맷으로 표현되므로 스키마를 확인하고자 @context
필드의 링크로 들어갔다. 그렇게 타고 들어가서 Note의 정의를 발견했는데 Object를 상속하였고, 상속받은 것 외에 자신만의 필드는 없어 보였다.
링크를 또 타고 들어가 summary의 정의를 볼 수 있었다. 설명은 아래와 같다:
A natural language summarization of the object encoded as HTML. Multiple language tagged summaries MAY be provided.
HTML로 스타일링할 수도 있고, 여러 언어별로 요약을 제공할 수도 있다고 한다. 아래 JSON은 문서에 있는 예제인데, 영어(en)와 스페인어(es), 중국어 간체(zh-Hans) 언어마다 요약을 각각 제공하는 것으로 보인다.
{
"@context": "https://www.w3.org/ns/activitystreams",
"name": "Cane Sugar Processing",
"type": "Note",
"summaryMap": {
"en": "A simple <em>note</em>",
"es": "Una <em>nota</em> sencilla",
"zh-Hans": "一段<em>简单的</em>笔记"
}
}
결론
Content Warnings
에 요약을 적는 건 적절한 용례이다! (?)- +) 댓글로 달아주셔서 알게된 놓친 부분인데,
Content Warnings
를 쓰면as:sensitive
확장 속성도 같이 추가되어서 Mastodon에서Content Warnings
를 요약으로 쓰는 것이 마냥 맞는 용례는 아닌 것 같다.
- +) 댓글로 달아주셔서 알게된 놓친 부분인데,
- 사용자가 주로 사용하는 언어로 작성하면, 애플리케이션 단에서 다른 요약들도 번역해서 자동으로 채워줄 수도 있겠다. (읽는 쪽에서 번역하는 게 나으려나)
@morealLee Dogeon 참고로 Mastodon 등에서 CW에 체크를 하면
as:summary
속성만 붙는 것이 아니라, as:sensitive
속성도 함께 켜집니다.
별 것 아니지만, Markdown 문법 가이드를 추가했습니다. Markdown을 모르는 분들은 거의 없겠지만, Hackers' Pub은 Markdown 확장 문법을 꽤 많이 지원하기 때문에, 이를 문서화할 필요가 있었습니다.
단문 작성 화면에서 “이미지 업로드” 버튼 왼쪽의 “Markdown 사용 가능” 링크를 누르시면 언제든지 Markdown 문법 가이드를 보실 수 있습니다.
Lee Dogeon shared the below article:
Vim이랑 Neovim은 어떻게 다를까?

Jaeyeol Lee @kodingwarrior@hackers.pub
주변에서 하도 계속 물어봐서 Vim과 Neovim의 결정적인 차이점을 문서로 남긴다. 당장은 생각나는대로 나열했기 때문에, 추후에 내용이 더 추가될 순 있다.
사용하는 언어부터 다르다 (VimScript / Lua)
Vim을 커스터마이징할때는 VimScript를 쓰지만, Neovim을 커스터마이징할때는 lua를 사용한다.
개인적으론 VimScript를 선호하진 않는데, vimscript 기반으로 짜여져 있는 플러그인의 소스코드를 읽는 것 자체가 고통스러웠던 경험이기도 했고, vimscript라는 언어 자체가 그닥 가독성이 좋은 편은 아니라고 생각한다. 기능적으로는 모자람이 없을 순 있으나, 읽을때도 고통스러운데 이걸로 스크립트를 짜라면 어지간하면 피할 것 같다.
반면. lua는 vimscript에 비하면 가독성이 적당히 나쁜 편은 아니다. 언어 자체의 기능으로 보자면 Python/Ruby/Javascript 등의 언어와 비교했을때 굉장히 좀 난해하게 느껴질 수는 있다.[1] 다만, lua는 macOS에서 사용하는 자동화 툴인 hammerspoon이라던가, 터미널 에뮬레이터인 wezterm이라던가 Unix 계열의 CLI 프로그램의 configuration을 작성하는데 있어서 사실상 De facto의 역할을 하고 있다. 언어 자체가 마음에 들지 않는 부분은 어느 정도 있긴 하지만, 가독성이나 개발편의성이 엄청 나쁜 것은 아니기 때문에 거부할 이유는 딱히 없다고 생각하고 쓰고 있다.
luarocks 패키지와 호환이 된다
Neovim을 커스터마이징할때 활용하는 lua는 luarocks라는 패키지매니저가 있기 때문에, 필요하다면 얼마든지 가져다 쓸 수 있다.
luarocks를 이용하는 Neovim 플러그인은 그렇게 많지는 않지만, (실질적인 쓸모의 유무를 떠나서) luarocks를 활용한 플러그인도 종종 보이긴 한다. 확실한건, lua 기반으로 개발되어 온 ecosystem을 등에 업고 언제든지 활용할 수 있다.
플러그인 생태계도 제법 괜찮은 편이다.
telescope / nvim-cmp / treesitter 같은 키워드 위주로만 찾아봐도 이걸 응용한 괜찮은 기능의 플러그인들을 많이 살펴볼 수 있다.
telescope
Neovim에서 사용할 수 있는 검색엔진이라고 생각해봐도 될 것 같다. 자세한 내용은 :help telescope
만 봐도 알 수 있겠지만, 파일 검색/패턴 검색 뿐만이 아니라 query를 넣을 수 있는 것이라면 어떤 것이든 다 해낼 수 있는 요술봉이라 생각한다.
예를 들면, git log를 검색한다던가, 현재 열어놓은 파일의 git history를 열람한다던가, 각각의 브랜치에 대한 git history를 표시하는 등의 git과 관련된 유용한 기능은 이미 네이티브로 들어가 있다.
여기서 좀 더 응용한다면, 아래의 예시와 같이 이용할 수 있다.
- 프로젝트를 구성하는 소스코드의 클래스/모듈/함수/상수 검색
- formatter/linter 오류 검색 (diagnostics)
- GitHub 리포지토리의 issue/PR 검색 -- pwntester/octo.nvim 참고
- throttling만 잘한다면 웹 요청이랑도 연결할 수 있다. 개인적으론 알라딘 검색 API와 연동하는 실험을 하고 있다.
nvim-cmp
이름에서 알 수 있듯이, 말 그대로 자동완성 기능을 위해 만들어진 플러그인 ecosystem이다. Neovim에서 자동완성 기능을 커스터마이징 해야 한다면, 바로 이 친구를 이용한다면 아주 간단한 문제가 된다.
pwd 기준의 경로 자동 완성 / emoji / 버퍼 내에서 반복적으로 사용되는 단어 위주의 자동완성 같은 사소한 것부터 Code Snippet / git commit sha1 해쉬 / GitHub author 자동 완성 / Language Server와 연동된 auto import 까지 입맛대로 자동완성 기능을 커스터마이징할 수 있다. 위에서 설명했던 telescope와 마찬가지로 쿼리할 수 있는 것이라면 어떻게든 활용할 수 있기 때문에 가능성은 무궁무진하다.
treesitter
사실 이게 왜 좋냐고 하냐면 많은 사람들에게 설득하는게 약간 어려운 난제이기도 하다. "굳이?" 라는 생각이 들 수도 있기 때문이다.
treesitter 자체는 파서를 만들어주는 제네레이터일 뿐이지만, 트리시터 기반으로 만들어진 파서가 활용도가 높기 때문에 유용함을 알고 있는 사람들은 잘 쓰고 있는 것 같다.
예를 들면, 커스텀 룰을 지정해주면 내가 탐험하고자 하는 소스코드의 범위를 탐색하는 것이 간단해진다. 왜냐면, scheme으로 커스텀 룰을 추가하는 것 자체가 일종의 트리 자료구조를 탐색하는 쿼리이기 때문이다.
알고리즘에 대한 배경지식이 있는 사람들한테는 좀 더 단순하게 설명하기도 하는데, "treesitter는 2차원 배열로 바라봐야 하는 소스코드를 트리로 바꿔서 생각할 수 있게 해준다." 이런 표현을 즐겨쓰는 편이다.
이에 대해 좀 더 직관적으로 와닿을 수 있는 예시는 ziontee113/syntax-tree-surfer인데, 함수가 정의되어 있는 위치를 바꾸는 demo 영상을 살펴보자. Syntax tree로 나타내면 두 함수의 위치는 Tree의 관점에서 보면 sibling으로 볼 수 있다. 두 함수가 정의되어 있는 위치를 바꾸는것은 사실상 트리 노드의 위치를 바꾸는 아주 간단한 문제로 환원이 된다.
그 외에도 두드러지는 점들
language server 지원도 나름 나쁘지는 않은 편인데, 제대로 세팅하려면 각각 language server마다 걸맞는 configuration을 해줘야 하는 수고로움이 생길 순 있지만, coc-nvim 세팅해놔도 개인적으론 크게 문제가 없었다.
Helix라는 신흥강자도 생기고 있는 모양이지만, 2023년 11월 기준, 2025년 3월으로는 크게 확 와닿을 정도로 삶에 혁신을 가져다 줄 만한 변화가 없기 때문에 당문간 관심을 가지는 건 보류
lua라는 언어의 불편함에 대해서는 따로 글로 작성하게 될 것 같다. 그렇다고 아예 못 쓰겠다라고 느낄 수준은 아니지만, 여러가지 면에서 인지 부조화를 느끼게 하는 부분이 있다는 것은 부정할 여지가 없다. ↩︎
@hongminhee洪 民憙 (Hong Minhee) 꼭 외래어만 그런 건 아니지만 ㅐ와 ㅔ의 혼선이 제법 있는데, 이를테면 lag 랙("렉"으로 틀림) 같은 사례가 있습니다. 그 밖에는 daemon 다이먼(동계어인 demon에 이끌려 "데몬"이 널리 쓰이지만, 애초에 demon의 올바른 표기는 "디먼"임) 같은 게 생각나네요. 뭐 알아도 그렇게 안 쓰는 사람이 너무 많아서 대부분 틀린 표기로 쓰게 되지만...
@yurume유루메 Yurume
@hongminhee洪 民憙 (Hong Minhee) "lag"의 외래어 표기법에 따른 표기는 "랙"이 아니라 "래그"입니다. 유성음으로 끝나기 때문에 "ㅡ"를 붙입니다. "log"가 "록"이 아니라 "로그"인 것도 이것 때문입니다.
해커스펍이 터졌었나보군요! (이상하다 아직 전원버튼 안 눌렀는데)
Hackers' Pub 타임라인에 내부적인 개선이 있었습니다. 이제까지는 타임라인을 렌더링하기 위해 실시간으로 복잡한 조건의 SQL
을 실행하는 방식이었지만, 이제는 글이 작성될 때 구독자의 수신함(inbox)에 글이 들어가는 방식으로 바뀌었습니다. 타임라인을 렌더링할 때는 각자의 수신함만 확인하면 되기 때문에 훨씬 조건이 간단해진 것입니다.
더불어, 같은 글을 여러 사람이 공유했을 때 타임라인이 같은 글로 도배되던 문제를 해결했습니다. 이제는 마지막에 공유한 사람의 글만 딱 하나 보이게 됩니다.
이번 변경에 관해 궁금하신 분은 f692909cdd5149c68ca5a91fb4e964042115ab83
커밋을 확인하시면 되겠습니다.
이 변경을 배포하다가 데이터베이스 스키마 마이그레이션이 PostgreSQL을 멈추게 하여 Hackers' Pub이 몇 분 동안 내려가는 일이 있었습니다. 마이그레이션 SQL이 너무 비효율적이라 그랬던 것인데요, Claude Code의 도움을 받아 하나의 비효율적인 SQL을 몇 개의 SQL로 나눠서 실행하게끔 고쳐서 해결했습니다. 이 역시 궁금하신 분은 33f2209f206bee84ddf5d1a7124527dade948610
커밋을 확인하시면 됩니다.
앞으로는 더 안정적인 서비스 운영을 위해 노력하겠습니다. 죄송하고 감사합니다.
Lee Dogeon shared the below article:
한국 소프트웨어 개발자들이 자주 틀리는 외래어 표기법

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub
전에 단문으로 올렸던 글을 지속적으로 갱신해볼까 싶어 게시글로 만들어 봅니다.
영어 | 틀린 표기 | 올바른 표기 |
---|---|---|
algorithm | 알고리즘 | 알고리듬 |
app | 어플 | 앱 |
application | 어플리케이션 | 애플리케이션 |
BASIC | 베이직 | 베이식 |
directory | 디렉토리 | 디렉터리 |
front-end | 프론트엔드 | 프런트엔드 |
launch | 런치 | 론치 |
license | 라이센스 | 라이선스 |
message | 메세지 | 메시지 |
method | 메소드 | 메서드 |
parallel | 페러렐 | 패럴렐 |
proxy | 프록시 | 프락시 |
release | 릴리즈 | 릴리스 |
repository | 레포지토리 | 리파지터리 |
shader | 쉐이더 | 셰이더 |
shell | 쉘 | 셸 |
게시글에 목차가 추가되었습니다. 게시글 안에 소제목이 있을 경우에는 목차가 보이게 됩니다. 가로로 넓은 화면에서는 오른쪽에 보이고, 모바일 환경처럼 가로로 좁은 화면에서는 제목 아래 본문 위에 보이게 됩니다.
.vimrc 파일 공유하던사람들끼리 창업해서 CLI 깎는 회사 만들고 6백만달러를 펀딩받았다는 이야기가 있다. 나는 이 이야기를 아주 좋아한다.
https://choubey.gitbook.io/internals-of-deno
Deno 런타임 내부 까보는거 입문은 이걸로 시작해야겠다
저… 제가 아마도 해커스펍 개발자분보다도해커스펍 서버와 가장 가까이서(2미터 거리) 쓰고 있는 유저인데…
느려요.
RE: https://hackers.pub/@hongminhee/0195d567-06d6-7437-a4eb-cf567adf9714
Lee Dogeon shared the below article:
Browser-Native Translation and Language Detection APIs Coming Soon

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub
Just reviewed the W3C draft for the Translator and Language Detector APIs. This is genuinely exciting development for web developers.
The proposal would add native browser support for:
- Text translation between languages
- Language detection of arbitrary text
- Both with streaming capabilities
No more relying on third-party translation services or embedding external APIs for basic language operations. All processing happens locally in the browser.
The API design is clean and straightforward:
// Translation example
const translator = await Translator.create({
sourceLanguage: "en",
targetLanguage: "fr"
});
const translatedText = await translator.translate("Hello world");
// Language detection example
const detector = await LanguageDetector.create();
const results = await detector.detect("Hello world");
// Returns array of detected languages with confidence scores
This will be a game-changer for multilingual sites and applications. The browser handles downloading appropriate language models and manages usage quotas.
The spec is still in draft form but shows promising progress toward standardizing these capabilities across browsers. Looking forward to seeing this implemented.
@xiniha 님께서 Hackers' Pub에 눈에 보이진 않지만 큰 기여를 해 주셨습니다. Drizzle ORM 베타 버전에서 쓸 수 있는 릴레이셔널 API v2를 Hackers' Pub 코드 전체에 적용하는 큰 패치가 바로 그것입니다.
기능적으로 눈에 바뀌는 것은 없겠지만, 아마 성능상으로는 약간의 개선이 있을 수 있습니다. 기존에는 복잡한 관계 필터를 서브쿼리 방식으로 해 왔는데, 릴레이셔널 API v2를 쓰면 JOIN
으로 바뀌는 것 같아요. 물론 PostgreSQL의 쿼리 최적화기가 뛰어나다면 두 방식 중 어떤 방식을 쓰든 같은 실행 계획을 수립할 것이므로 성능 차이가 없을 수도 있지만요. 아니면 더 느려질 수도 있겠죠. 거기까지 세세하게 비교 테스트해보진 않았습니다. 😅
참고로 해당 변경은 이미 배포된 상태입니다. 아무튼 고생해주신 @xiniha 님께 박수 부탁드립니다. 👏
Hackers' Pub의 타임라인 탭 중 “언급” 탭이 “언급 및 인용” 탭으로 바뀌었습니다. 날 언급한 글 뿐만 아니라, 내가 쓴 글을 인용한 글도 함께 보이게 됩니다. 본격적인 알림 기능이 생기기 전까지는 이 탭을 잘 활용하시면 좋을 것 같습니다.
https://news.hada.io/topic?id=19948
나는 프로그래밍을 Solaris에서 시작했고, FreeBSD의 오랜 팬이자 사용자였지만, 이제 와서는 BSD를 권하지 않음. 내 개인적으로도 이젠 더이상 쓰지 않고 있고. 물론 BSD가 특정 시나리오에서는 매우 뛰어난, 그리고 일반적으로 좋은 서버 OS라는 점은 지금도 유효하지만, 더이상 개인이 사용할 우위점은 거의 없다고 생각함.
- 리눅스가 충분히 안정화되었고, 가장 안정적인 OS를 찾는다면 RHEL(+클론들)을 쓰면 됨. 대형 유저들이 많으며, 전문가 집단이 구성하고 충분히 테스트한 OS임.
- 리눅스가 de facto standard여서, 더 효율적인 솔루션들이 있더라도 작은 차이라도 큰 특수 분야가 아니라면 굳이 다른 방법을 찾는 비용을 정당화하기 어려움. 무엇보다 유지보수, 확장, 인프라 이전 등 모든 면에서 리눅스가 제일은 아닐지 몰라도 충분히 쌈.
- 보안에 있어서도 OpenBSD의 품질은 대단하지만, 대형 리눅스 배포판들 또한 취약점 패치 속도에서 매우 빠른 편이고 보안 툴들도 잘 갖추고 있음. 이제 양자간 개발자, 사용자 숫자의 차이는 인원과 설계의 질로 메꿀 수 있는 수준이 아니라고 봄.
Lee Dogeon shared the below article:
Hacker's Pub에 입문한 한국어권 여러분을 위한 안내서

Jaeyeol Lee @kodingwarrior@hackers.pub
먼저 터를 잡은 사람으로서 Hacker's Pub에 오신 여러분들을 환영합니다. 현재 추정하건데 150명이 넘는 분들께서 Hacker's Pub에 들어오고 계신 것 같은데요. 물이 들어오면.... 노를 젓는게 인지상정이겠죠? 이 서비스가 만들어진 초기부터 관심을 가져왔고, 이 서비스의 가능성을 믿는 사람으로서 여러분에게 안내를 드리고자 합니다.
Hacker's Pub은 무엇을 하는 곳인가요?
Hacker's Pub은 소프트웨어 업계에 몸을 담고 있는 여러분들의 찰나의 생각, 혹은 장문의 정제된 생각들을 자유롭게 개시할 수 있는 소셜 네트워크 서비스이자, 블로깅 플랫폼입니다. 여러분은 그때그때 드는 생각들을 올려서 다른 사람들과 자유롭게 소통이 가능합니다.
Hacker's Pub 이라는 이름은 중의적인 의미를 가지고 있습니다.
- ActivityPub 프로토콜을 지원하는 해커들을 위한 커뮤니티
- Hacker's + Pub - 해커들이 자유롭게 웃고 떠들 수 있는 주점
Hacker's Pub이라는 커뮤니티에 애정을 가지고 있는 저로서는, 여기에 들어오시는 모든 분들이 Pub에 들렸다가 가는 것처럼 편안한 공간으로 느껴지시길 바랍니다. 우리 모두가 편안함 경험을 누릴 수 있도록 초대제로 운영이 되고 있습니다. 또한, 사회적 합의로서 행동강령도 마련되어 있으니 한번 참고해보시면 좋을 것 같습니다.
ActivityPub 프로토콜이란 무엇인가요?
Hacker's Pub은 ActivityPub 프로토콜을 지원하는 커뮤니티서비스입니다. 그렇다면, ActivityPub은 무엇일까요? ActivityPub은 웹 상의 분산형 소셜 네트워크를 구현하기 위한 국제 표준 프로토콜로, W3C에서 표준화되었습니다. Mastodon, Misskey, Pleroma 등 다양한 SNS 서비스들이 이 프로토콜을 기반으로 상호 호환되며 자유롭게 소통할 수 있도록 지원합니다. 쉽게 말해, 어떤 SNS를 사용하더라도 경계를 넘어 서로 소통할 수 있도록 돕는 것이 ActivityPub의 핵심입니다.
ActivityPub에 대한 자세한 설명은 여기가 정말 잘 되어 있으니 관심있는 분들은 한번 정독해보시는걸 권장드립니다.
Hacker's Pub을 이용하는 여러분들은 ActivityPub 프로토콜을 사용하는 모든 SNS 서비스(Mastodon, Misskey 등)의 사람들과 연결이 되어 있으며, 심지어 Bluesky에 있는 사람들과 연결이 되어 플랫폼의 경계가 허물어져 있는, 사실상 초연결적인 사회의 경험을 누릴 수 있습니다.
Hacker's Pub 생태계에 기여하기
Hacker's Pub은 아직 Early Stage 단계이며, 여전히 개선하고 발전해야 하는 여지는 있습니다. Hacker's Pub은 모든 것이 오픈소스로 개발되는, 전적으로 개방된 커뮤니티 서비스이기 때문에 여러분도 생태계의 발전에 기여할 수 있습니다. 해커들을 위한 공간이니만큼, 어느 누구에게도 기여는 열려있습니다. 여러분은 여기에서 실시간으로 기여에 참여하실 수 있습니다.
경우에 따라서는 Hacker's Pub에 당장은 기여하기가 어려울 수도 있습니다. ActivityPub 프로토콜에 대한 사전지식이라던가 혹은 서비스가 만들어진 기술스택에 대한 사전지식 같은 것들이 진입장벽이 될 수는 있을 것이거든요. 그렇다고 해도 괜찮습니다. ActivityPub에 대한 이해는 한국 연합우주 개발자 모임에서 함께 커뮤니티 차원에서 함께 알아가면 됩니다.
더불어, 시간이 지나면 Hacker's Pub 주변 생태계도 더더욱 발전할 수 있을 것이라 믿습니다. 당장은 서버와 클라이언트가 합쳐져서 개발되고 있는 형태이지만, 조만간 서버와 클라이언트가 분리되어서 개발될 예정이고, Mastodon/Misskey 같은 다른 ActivityPub 기반의 서비스가 그래왔듯, 우리만의 Hacker's Pub 클라이언트를 만들게 되는 날도 올 것입니다.
Hashnode 처럼 Hacker's Pub 블로그 템플릿 같은 것이 만들어지는 미래, 기대되지 않나요?
함께 만들어가는 Hacker's Pub
Hacker's Pub은 해커들을 위한 공간인 동시에, 모든 분들이 각자의 색깔과 생각을 마음껏 표현할 수 있는 열린 무대입니다. 우리 커뮤니티는 상호 존중과 신뢰를 기반으로 하며, 이를 위해 마련된 행동강령을 준수하는 동시에, 우리 모두의 다양한 의견과 피드백이 존중될 수 있는 공간입니다. 만약 새로운 아이디어나 개선 사항이 있다면 언제든지 의견을 공유해도 됩니다. 우리 모두의 참여가 바로 Hacker's Pub의 미래를 밝게 만드는 원동력입니다.
Hackers' Pub에서는 Markdown 내용 안에 HTML을 어느 정도 (XSS을 허용하지 않을 정도) 허용하고 있습니다. 그래서 <abbr>
, <kbd>
, <ruby>
같은 태그를 써서 좀 더 풍성한 서식화가 가능합니다.
블루스카이를 연합우주보다 먼저 썼고, 해커뉴스에서 관련 주장에 대해서 꽤 싸우기도 한 입장에서 민희님의 글 〈Bluesky는 X의 훌륭한 대안일 수 있지만, 연합우주의 대안은 아닙니다〉에 대한 반대 의견을 제시하고자 한다. 이 의견이 연합우주에 대한 전면적인 비판이 아니라는 것을 의견을 제시하기에 앞서 확실히 해 둔다(그랬다면 Hackers' Pub에 들어 올 일이 없었겠지).
탈중앙화는 매력적인 개념임이 틀림 없다. 인터넷의 많은 중요한 요소들이 어느 정도 탈중앙화되어 있으므로 탈중앙화가 인터넷의 장점들에 큰 몫을 했다는 생각을 쉬이 할 수 있고, 어느 정도는 그게 사실이기도 하니까. 하지만 엄밀히 말하자면 탈중앙화는 기술적인 특징이지 그 자체로 장점이 아니며, 탈중앙화가 장점으로 작용하려면 연결고리가 필요하다. 이를테면 비트코인을 위시한 암호화폐는 본디 비잔틴 실패까지 대비할 수 있는 강력한 탈중앙화를 장점으로 내세웠으나, 결국 화폐로서 제대로 사용되기 시작하자 현실 경제와의 커플링 때문에 그 "장점"이 크게 희석되고 말았다. 현 시점에서 암호화폐는 무에서 유의 신뢰를 창조하여 신용화폐의 요건을 충족하는 데까지는 성공했고 그것만으로도 역사적인 일이기는 하지만, 그게 탈중앙화랑 무슨 상관이 있느냐 하면 글쎄올시다.
블루스카이가 연합우주보다 덜 탈중앙화되어 있음은 분명하다. 민희님의 글에서 지적되었듯, 블루스카이가 이런 선택을 한 가장 큰 이유는 온전한 소셜 네트워크 기능을 위해 전역 뷰가 필수적으로 필요하다고 보았기 때문이다. 반대로 말하면 연합우주는 더 탈중앙화를 하기 위하여 전역 뷰를 포기했는데, 이 때문에 연합우주에서의 "소셜 네트워크"는 트위터/X와는 구조가 크게 다르다. 노드 규모가 문턱값에 다다르지 못하면 다른 노드에 있는 사용자를 찾아서 팔로해야만 온전한 소셜 네트워크 구성이 가능한데, 연합우주 안에서는 이런 외부 사용자를 찾는 구체적인 방법을 제공하지 않는다. 물론 인터넷과 똑같이 검색 엔진이 존재할 수야 있겠지만, 크롤링으로 인한 부하와 프라이버시에 대한 의견 차이 때문에 현실적으로 작동하는 연합우주 내 검색 엔진은 없다고 알고 있다. 따라서 연합우주에서 소셜 네트워크의 구성은 연합우주 바깥의, 보통은 중앙화되어 있는, 다른 소셜 네트워크(이를테면 현실 인간 관계)를 빌어야만 하는데, 이러면 탈중앙화가 큰 가치가 있을까?
한편으로는 전역 뷰가 소셜 네트워크의 단점이라고 주장할 수 있는 여지도 있다. 트위터/X를 오래 써 본 사람이라면 다 알겠지만 한 무리의 사람들이 다른 의견을 가진 무리와 충돌하는 주된 통로는 검색이나 해시태그를 통한 노출, 즉 전역 뷰이기 때문이다. 그러나 현실의 규모 있는 연합우주 노드들을 살펴 보면 각 노드가 곧 한 무리에 대응하는 식으로 충돌을 미리 회피하는 형태로 구성되지, 딱히 이런 충돌을 막기 위한 접근을 가지고 있는 것은 아니다. 노드 운영자를 위해 차단하는 걸 추천하는 서버 목록 같은 게 돌아다니는 건 연합우주 바깥의 일이지 않는가. 결국 전역 뷰의 역할을 대체하는 소셜 네트워크 바깥의 또 다른 소셜 네트워크가 존재할 것이기에, 우리가 소셜 네트워크를 어떤 이유로든 유용하다고 여긴다면 전역 뷰가 없는 게 장점이 될 수는 없다.
모든 이들이 이런 사고 과정을 가지고 블루스카이나 연합우주를 선택했다고 생각하진 않지만, 적어도 현 시점에서 사용자들은 블루스카이(이 글을 쓰는 시점에서 약 3360만명)를 연합우주(FediDB와 Fediverse.party로부터 추정할 때 최대 1530만명)보다 선호하는 것은 틀림이 없다. 게다가 블루스카이의 규모는 최근 1년 사이에 10배 불어난 것이고, 조금 장애가 있었지만 현재는 잘 동작하는 것으로 보인다. 위의 논의와 결합해 보면, 블루스카이는 정석적인 스케일링에 성공하고 있는 반면 연합우주는 스케일링 문제를 회피하기 위해 온전한 소셜 네트워크의 구성을 포기했다고 볼 수도 있는 대목이다. 블루스카이가 못미더운 부분은 분명히 존재하지만, 연합우주가 더 좋은 소셜 네트워크 경험을 제공한다고 가정하고 블루스카이의 단점을 제시할 수는 없다. 마치 암호화폐를 논할 때 장점만 말할 수 없는 것과 마찬가지로 말이다.
RE: https://hackers.pub/@hongminhee/2025/bluesky-a-good-alternative-to-x-not-to-the-fediverse
얼굴인식 사진공유 카메라앱 슈티를 함께 만들 분을 찾습니다. 앱은 출시되어 있어 써보실수 있습니다. 이번달 내로 페디버스 연동을 끝내면 제가 생각한 MVP는 완성입니다. 앞으로도 개발해야할 부분들이 많고, 개중에 기술적으로 흥미로운 문제들도 다수 있습니다.
지금 2025년 상반기 투자유치를 목표로 팀 빌딩을 하고 있습니다. 관심 있으신 분, 또는 잘 모르겠지만 이야기를 나눠보고 싶은 분도 bgl@gwyng.com으로 편하게 연락주세요.
Lee Dogeon shared the below article:
파이어폭스의 숨은 기능

가을별 @gaeulbyul@hackers.pub
내 Hackers' Pub 계정은 좀 더 정보있는 글을 올리는 블로그처럼 운영하면 좋을 거 같다.
그래서 이번 글은 내가 예전에 마스토돈에 공유했던 글 중 파이어폭스에 숨겨진, 그래서 비교적 잘 알려지지 않은 기능을 소개하는 글을 모아보았다. 추가로 아직 공유하지 않았던 기능도 하나 덧붙였다.
주소창에서 쓰는 계산기 및 단위변환
파이어폭스의 about:config
에는 브라우저 설정페이지에선 보이지 않는 각종 실험적인 기능이 숨겨져있기도 하다. 그 중 하나는 주소창에서 사칙연산 수식을 입력하면 드롭다운에 이를 계산한 결과값을 보여주는 계산기 기능인데, 예를 들어 "12 + 12"를 입력하면 24가 아래에 나타난다. 이는 about:config
에서 browser.urlbar.suggest.calculator
를 true
로 설정하고나면 이 기능을 사용할 수 있다.
단위변환 기능도 숨어있는데, 예를 들어 주소창에 "30 cm in inches"를 입력하면 "11.81102362 in" 가 나타난다. 이는 about:config
에서 browser.urlbar.unitConversion.enabled
값을 true
로 설정한 뒤 사용할 수 있다.
설정된 검색엔진에 따라, 저 옵션이 설정하지 않더라도 검색제안을 통해 위와 비슷한 기능을 쓸 수 있기도 한다. (구글이나 빙 등)
페이지내 미디어 모아보기 & 내려받기
보통 웹 페이지에서 이미지를 저장할 땐 마우스 우클릭 메뉴를 사용하거나, 우클릭이 막혀있을 경우 개발자도구를 사용하여 URL을 알아내어 저장한다. 파이어폭스에선 방법이 하나 더 있는데, 바로 페이지 정보를 활용하는 것이다.
이 기능은 Ctrl + I 단축키나 Alt 메뉴 -> 도구 -> 페이지 정보를 통해 들어갈 수 있는데, 여기서 "미디어" 탭에 들어가면 해당 페이지에 들어간 미디어 즉, 이미지와 배경 이미지는 물론, HTML5 오디오나 동영상까지 모두 모아서 보여준다.
게다가 여기에 나타난 파일들은 우클릭/개발자도구 방지같은 방해없이 저장하는 것도 가능하고, 심지어 여러 파일을 선택해서 한꺼번에 저장하는 기능도 있다!
다만, 여러 파일을 저장하는 기능은 이름이 겹치는 파일을 빼먹는 경우가 있어서 이 점이 아쉽다. 또한 이 기능을 가지고 유튜브나 일부 웹툰사이트에선 미디어를 저장할 수 없다. (URL 앞에 blob:
가 붙어있으면 여기서 저장할 수 없는 파일이다.)
이스터에그: 파이어폭스에도 숨겨진 게임이 있다.
크롬에선 인터넷 접속이 안 될 때 공룡이 달리는 게임이 숨어있다. MS에지(Edge)에선 서핑하는 게임이 숨어있고 비발디 브라우저에선 Vivaldia라는 게임이 있다. 파이어폭스에도 이런 숨겨진 게임이 있는데, 주소창 입력으로 진입할 수 있는 타 브라우저들과는 달리 너무 꼭꼭 숨겨서 다른 브라우저에 비해 찾기가 힘들다.
파이어폭스에서 도구모음 사용자지정(Customize toolbar)에 들어간 뒤, 가변 공간(Flexible Space)만 남기고 나머지 항목을 모조리 도구모음이나 더보기 메뉴에 넣으면 아래에 유니콘 에모지 버튼이 생긴다. 이를 누르면 Pong을 90도 돌린거 같은 게임이 나타나며 키보드의 좌우 방향키로 조작하여 플레이할 수 있다.
개발자도구 UI크기 조절
이건 사실 Chromium계 브라우저에서도 있는 기능이다. 개발자나 파워유저의 경우 종종 브라우저의 개발자도구를 사용하기도 하는데, 사람에 따라, 혹은 사용하고 있는 모니터에 따라 개발자도구의 UI나 글자 크기가 너무 작게 느껴질 수도 있다.
그럴 땐 개발자 도구에서 Ctrl + (+ 혹은 -) 단축키를 누르거나 아니면 Ctrl + 마우스 휠을 통해 UI 크기를 조절할 수도 있다.
Lee Dogeon shared the below article:
2025 Q1 Review

Jaeyeol Lee @kodingwarrior@hackers.pub
작년 10월 쯤부터 강남에 파견근무를 가게 되었다. 간만에 돈벌이가 나쁘지 않은 생활, 요즘 받는거에 비하면 월급 두배 이벤트를 하고 있는데, 그만큼 너무 바빠졌다. 주말도 쉬지 않고 일했고, 설연휴도 삼일절 연휴도 쉬지도 못하고 일했다. 그러다 보니, 책을 읽을 시간도 없을 뿐더러, 사람을 만나러 다닐 여유도 거의 없다시피 했다. 일정을 잡는 것도 눈치봐야 하는 수준으로 바빠졌고, 이 일정이 언제 끝날지도 모르겟다.
그래서 블로그에 근황을 남기자니, "네.. 그냥 뺑이치고 있습니다..." 라고 밖에 요약이 되지 않는다.
요즘 근황이 어떻냐면....
블로그에 쓸만한 근황은 잘 없는 것 같지만, 그래도 몇가지 변경사항은 있는것 같아서 기록이라도 남겨야겠다. 대외활동을 하게 될 일은 당연히 없었어서 타임라인을 나열하기도 어렵고, "그냥 요즘 이런 변화가 생겼고, 이런 생각을 하고 있습니다" 정도로 남겨두겠다.
노트를 사서 끄적이는 습관을 들이려고 하는 중이다
삶에 변화를 좀 줘볼까하는 마음가짐에 프랭클린 플래너랑 속지를 구매했다. (사실 이런짓은 2016년/2020년 시도해본 적도 있었다) CEO 사이즈가 간편하기도 하고, 펜을 꽂을 수 있는 공간도 있어서 들고 다니면서 뭔가를 끄적이기에도 좋다.
<script data-allowed-prefixes="https://social.silicon.moe/" async src="https://social.silicon.moe/embed.js"></script>Post by @kodingwarrior@silicon.moeView on Mastodon
요즘은 일할때 아에 A4 용지 하나 꺼내서 거기다가 해야할 일들 나열하고, 어떤 Sub task를 해야하는지 시각적으로 쪼개기도 하는데, 키보드로 타이핑해서 할 일을 관리하는 것보다 역설적으로 더 관리가 잘 된다. 하나하나 남김없이 기록으로 남겨야겠다는 강박을 가지면 그것도 그것대로 집중이 안되었던 것 같다. 필요하면 그때그때 하나의 축약된 스냅샷을 남긴다면 모를까.
Getting Things Done 에 따르면, 할 일 관리 내지는 생산성의 끝판왕은 펜과 종이로 충분하다고도 설명하곤 했었는데, 왜 그런지는 요즘 들어서 실감하고 있다. 그렇다고, Vim을 사용하는 워크플로우가 별로이냐면 그것도 아니다. 각자, 담당할 수 있는 영역이 다를 뿐이고, 시각화가 필요하거나 시각적인 정보의 자유로운 배치를 원한다면 마우스로 어거지로 배치하느니 차라리 펜과 종이만한게 없다.
지하철 타고 다닐때나 버스를 타고 다닐때, 종이책을 들고 다니면서 읽거나 아이패드로 책을 읽곤 하지만, 책 자체가 내용이 많은건지 내 처리속도(1분당 1-2페이지)가 느린건지 유의미하게 읽는 양이 그렇게 많지는 않다. 꾸준히 읽는다는 것 자체에 의미를 둘 수는 있긴 하겠지만, '찔끔찔끔 읽으면서 내가 가져갈 수 있는게 무엇인가?'라는 실용적인 관점에서 접근해보니, 책 읽는데 시간을 들이기보다는 조금이라도 생각나는 것들을 다이어리에다가 기록이라도 남겨두면 이것들을 조합해서 밀린 계획들을 조금이라도 정리도 할 수 있고, 블로그에 글도 올리고, 블로그에 글을 올리겠다고 밀린 것들도 청산할 수 있고 일석이조 아닌가?
물론 책을 읽을 시간이 많으면 베스트겠다.
슬슬 취준을 시작하고 있다
지금 진행중인 3년이 넘는 계약도 슬슬 끝나간다. 취업 시장에 나올 수 있을때까지 한 6개월~1년 정도 남았다고 볼 수 있는데, 밥벌이를 하면서 취업 준비를 하기도 적당한 시기다. 사실은, "취업 준비"라는걸 제대로 해본 적도 없었다. 지금까지 해온 밥벌이도 그냥 코딩테스트는 그냥저냥 통과해서 그 운빨로 인턴을 시작하기도 했고, 그 다음부터는 지인(혹은 2차 지인)이 다니는 회사에 공식적인 전형이 없이 일을 해오긴 했었다. 그래서, 취업 준비를 하는 것도 이번이 처음이다.
여기에서도 간단하게 언급하긴 했었는데, 취준을 하게 된다면 프론트엔드 직군을 알아보거나 혹은 풀스택 직군을 알아보게 될 것 같다. 프론트엔드 직군을 생각하게 된 이유는 아래와 같다.
- 돈이 되는 제품을 만드는건 결국 프론트에서 시작한다.
아무리 기능이 많더라도 사용성이 구리거나 이쁘지도 않다면, 그걸 쓰려고 하는 고객도 잘 없다. 그것은 즉슨 돈벌이가 되지도 않는다. 기능을 특정 고객에게 맞춤형으로 개발한다고 한들, 사용성이 구리거나 이쁘지도 않으면 다른 경쟁업체에게 빼앗기기 일쑤다. 돈이 되는 일을 하고 가치를 창출하려면 프론트엔드를 하는게 불가피하다는 결론에 도달했다.
- 이왕 피할 수 없으면, 그냥 이대로 커리어로 끌고 가야겠다는 생각이 들었다.
본업은 분명히 백엔드로 시작하긴 했었지만, 실무에서 주로 하게 되었던 일들은 프론트엔드 할 사람이 없거나 혹은 일손이 모자라서 짬처리를 하는 일이었다. 거쳐갔던 회사 중에는 신중하게 기획하고 제품을 잘 만드는 것에 집중하고 기술스택을 가리지 않는 좋은 회사도 있었지만 이 경우는 짬처리와는 거리가 멀었다. 짬처리를 당하든, 내가 자발적으로 하게 되든, 결국에는 프론트엔드는 피할 수 없는 일이 되어왔다.
피할 수 없으면, 이걸로 계속 밥벌이를 하고 있으면, 그냥 이걸 내 커리어로 들고 가는게 맞지 않을까? 라는 생각이 들었다. 어차피, 백엔드도 그렇게 깊게 하지도 않았으니 프론트엔드가 손에 맞아가는 이 시점에 프론트엔드로 방향 트는 것도 방법이겠다 싶다.
프론트엔드 취준을 생각하면서도 걱정이 든다
프론트엔드 쪽으로 취업을 하려고 생각은 하고 있지만, 이래저래 걱정은 많다. 가장 먼저 드는 생각은, 내가 프론트엔드 개발을 할 때는 손이 그렇게 빠르지가 않다. Figma를 보면서 작업하면 금방이라고 느끼는 사람도 있겠지만, 하루에 10페이지-20페이지를 금방 찍어내는 사람이랑은 속도 차이가 좀 있는 것 같다.
거기다 처음부터 다시 배워야 하는 수준이다. 백엔드도 그렇게 깊게 하지는 않았지만, 프론트엔드는 더더욱 구조를 생각하면서 짜왔던 편도 아니거니와, 돌아만 가면 되는 수준으로 야매로 짜오긴 했다. 컴포넌트 나눠서 개발하는건 당연히 기본이긴 하지만, 잘 나누는지는 모르겠다. 그나마, "CSS는 과학이다"라는 마음가짐이었어서 CSS는 어느 정도 익숙하지만 딱 거기까지만인 것 같다.
지금까지 커리어를 이어오면서, 가장 취약했던 것도 사실은 프론트엔드이기도 하다. 퍼블리싱을 입히는 작업이 가장 괴롭게 느껴지기도 했었고, 다른 작업보다 심리적인 저항감이 있었어서 상대적으로 시간이 오래 걸리기도 했었다. (ADHD의 영향이 있어서일지도 모른다) 오히려 약점인 분야로 취업을 생각하고 있는 것도 어떻게 보면 이상하기도 하지만, "나는 프론트엔드 개발자다" 라는 마음가짐으로 임하게 된다면 그나마 저항감이 덜어질 것 같다.
당장은 할 수 있는 것부터 하고 있다
프론트엔드 개발자로서 어필하려면, 당장은 프론트엔드 개발자로서 포트폴리오가 될만한 것들을 만들어야 한다. 그러면서, 더더욱 의욕을 잃지 않을만한 것을 찾아서 만들어야 한다. 그래서 요즘은 나도 쓰고 남한테도 쓰라고 권장할 수 있는 앱을 만들려고 시도하는 중이다. 이 글을 쓰고 있는 Hackers Pub에 기여할 방법을 찾아보기도 하고, 직접 Mastodon 클라이언트를 만들고 있기도 하다. 다음 분기에는 꼭 출시하는게 목표다. 면접이나 과제 전형 준비는.... 일단 맞으면서 배워야겠지..
그래도 Full-stack 엔지니어(요즘 용어로는 Product 엔지니어) 라는 선택지도 완전히 버리지는 못해서 백엔드를 해야한다면 그때그때 습득하면 될 것 같다.
지금까지 읽은 책들
위에서 언급했다시피, 책 읽을 시간도 거의 확보하지 못했다. 집 - 사무실 - 집 - 사무실 루틴을 반복하는 것도 모자라서 최소 일주일에 한번 이상은 사무실에서 밤새기까지 해서 책을 읽을 정신적인 여력 조차도 없었다.
그나마 읽은 것들을 나열하자면....
- 또라이 제로 조직 (No Asshole Rule)
- 개인적으로 별로였다. 어떤 특징을 가진 사람을 또라이라고 규정하는 방식이나, 또라이라고 하는 사람이 조직에 얼마나 해로운지를 그럴듯한 설명을 하고 있지만, 이것도 주관적인 기준에 따라 다를 수 있기 때문에 평범한 사람도 또라이로 지목이 되어서 따돌림을 당하고도 남는 사회다.
- 일부는 납득은 되지만, 어조가 너무 노골적인 책이었어서 개인적으론 별로였다. 노골적인게 누군가에겐 사이다일 순 있겠지만, PTSD 있는 사람들에겐 피하라고 하고 싶은 책이다.
- RAG에 대한 책을 읽긴 했는데, 아직 공식적인 제목은 나오진 않았다. JPub에서 협찬을 받았지만, 출간 소식이 공식적으로 올라오면 그 때 링크를 달아두겠다.
- 큐레이션 : 정보 과잉 시대에서 쓸모에 맞게 조합해서 전시하는 것만으로도 어떤 가치를 전달할 수 있는지 잘 설명해주는 책이다. 알고리즘 기반의 추천이 어떻게 보면 이 시대의 큐레이션이라고 볼 수 있지 않을까?
- 노 필터(-ing) : 인스타그램 창업 스토리를 다루고 있는 책인데, 지금 읽고 있는 중이다. "사진을 찍고, 공유한다"라는 핵심적인 기능을 파고 들어서 시장에서 독보적인 위치를 차지해온 서사가 재밌다. 근데, 책 읽을 시간도 계속 없어져서 어느 시점부터는 맥락이 날아갈 것 같다.
And...?
이젠 좀 바쁜 것도 끝이 보이고, 이젠 진짜 하고 싶은거 많이 하면서 다음 분기를 보내고 싶다.
- Vim 행사 열기
- 좀 더 초보자들 친화적이고, 좀 더 많은 사람들에게 와닿고, 특히 Vim 자체에 어려움을 겪는 학생들에게도 Vim에 대해 가지는 "접하기 어렵다" 라는 고정관념을 타파할 수 있는 행사를 여는게 목표다.
- 지난 주부터 서베이를 돌렸는데, 44명이나 되는 분들이 응해주셨다. 이미 큰 행사를 열 것으로 계획하고는 있었지만, 정말 큰 행사가 될 것 같다
- JLPT N3 따기
- 듀오링고 일본어 모든 섹션을 다 완주하고 나서 자신감이 생겼다. 한자를 공부하는게 좀 고역이긴 하겠지만, 쪼끔이라도 잠깐 훑어보면 되지 않을까?라는 나이브한 생각이긴 하다. 어차피, 일본으로 넘어가는게 목표이기도 하겠다, N3 따는 걸로 시작해서 그 다음은 N2, 그 다음은 N1 점진적으로 따려고 한다.
- 일본 이민가기 프로젝트... 성공하겠지...?
- 만들고 있는 Mastodon Client를 플레이스토어에 출시하기