bgl gwyng

@bgl@hackers.pub · 61 following · 63 followers

슈티를 함께 만들 팀을 만들고 있습니다. 관심 있으신 분, 또는 잘 모르겠지만 이야기를 나눠보고 싶은 분도 bgl@gwyng.com으로 편하게 연락주세요.

GitHub
@bglgwyng
shootee
www.shootee.io
1

엔드포인트 솔루션이나 네트워크 장비를 운영하다 보면 그 솔루션 본연의 역할을 지고지순(?) 하게 지키기보다는 뭔가 민원을 해결하는 예외 처리에 리소스를 투입할 때가 많은데 그럴 때마다 뭔가 법을 어긴 것 같고 마음이 안 좋다.

7

러스트가 어렵다는 이야기에는 러스트의 특징을 이질적으로 느끼는 경우가 많아서도 있겠지만…어렵다는 말의 재생산이 어렵다는 이미지를 더 굳히는 것 같기도 합니다. 일종의 악순환이라고도 생각하는 게…어렵다는 이미지를 가지고 보면 개념 익히는 것도 어렵고 컴파일 에러를 해결하는 과정도 좀 더 고되게 느낄 수 있거든요. 러스트에 대한 이미지를 어렵다고 생각할수록, 갖고있는 배경지식이 러스트와 이질적일수록 어렵다고 느끼는 사람들이 많은듯합니다

4

Hackers' Pub 저장소에 보내주신 @perlmint 님과 @morealLee Dogeon 님의 CSS 및 PWA 관련 패치들이 모두 적용되어 배포까지 완료되었습니다.

여러분의 많은 기여 감사합니다. 🙏

참고로 현재 hackers.pub에 배포된 게 어떤 버전인지 알고 싶다면 https://hackers.pub/nodeinfo/2.1에 들어가셔서 software.version을 보시면 됩니다. 버전의 마지막 부분인 빌드 넘버가 Git 커밋 해시입니다.

2
7

Rust로 작성한 JPEG XL 디코더, jxl-oxide의 버전 0.12.0을 릴리스했습니다. https://github.com/tirr-c/jxl-oxide/releases/tag/0.12.0

CMYK 프로파일 등 복잡한 ICC 프로파일을 지원하기 위해 기존에 사용하던 Little CMS 2 (lcms2) 에 더해, Rust로 작성된 색 관리 시스템인 moxcms 지원을 추가한 것이 주요 변경사항입니다. CLI 툴의 기본 CMS는 아직 lcms2이지만 --cms moxcms 옵션으로 moxcms를 사용할 수 있습니다.

8
4

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
  }
}
4

bgl gwyng shared the below article:

논리적이 되는 두 가지 방법 - 논리와 저수준(Low-level) 자료 표현(Data representation) (2 편 중 1 편)

Ailrun (UTC-5/-4) @ailrun@hackers.pub

논리적인 말?

언제 어떤 문장이 "논리적이다"고 할 수 있을까? 일상적으로는 우리는 이를 남용해 의미가 있어 보이는 것을 "논리적이다"라고 수식하고는 한다. 그러나 필자는 이 남용이 "논리적"이라는 표현을 너무 가볍게 보고 있는 것이라 말하겠다. 어떤 것이 진정 논리적이기 위해서는 의미가 있는 것은 물론이거니와 최소한 다음의 두 조건을 더 만족해야한다.

  1. 몇몇 가정으로부터 그것을 증명해야 한다.
  2. 증명에 쓰인 체계가 모순을 보일 수 없어야 한다.

이 글에서는 이 중 1 번을 중점으로 설명하여 "좋은 가정 아래" 어떤 것이 논리적임을 증명하는 두 방법에 대해 다루어 볼 것이다. 두 방법 중 하나는 함수형 언어로 쓰인 프로그램과 유사한 구조를 가지며 나머지 하나는 일반적인 함수형 언어와는 상이한 구조를 가진다. 이 중 두번째 방법에 대해서는 (약간의 부정행위를 하면…\ldots) 2 번을 간단하게 보일 수 있기 때문에 이 또한 다룰 것이다.

논리적 증명의 대상

어떻게 어떤 것이 논리적이라는 걸 증명할 수 있는 지에 설명하기에 앞서 짚고 넘어가야 할 매우 중요한 요소가 하나 있다. 바로

질문

무엇이 논리적이냐?

는 것이다. 이 글에서는 철학적인 복잡도를 덜기 위해 이 대상을 다음과 같이 단순히 설명할 것이다.

논리적일 수 있는 대상은 명제(proposition)에 대한 판단(judgment)이다.

여기서 명제는 다음과 같이 정의되는 대상이다.

  1. 단위 명제(Atomic Proposition) AA는 명제이다.
  2. 참 명제(True Proposition) ⊤\top은 명제이다.
  3. 거짓 명제(False Proposition) ⊥\bot은 명제이다.
  4. 명제 PPQQ가 있을 때, P∧QP \land Q(PP 그리고 QQ)는 명제이다.
  5. 명제 PPQQ가 있을 때, P∨QP \lor Q(PP 또는 QQ)는 명제이다.
  6. 명제 PPQQ가 있을 때, P→QP \to Q(PP라면 QQ)는 명제이다.

여기서 단위 명제란 우리가 선택한, 더이상 세밀하게 분석하지 않을 논리적인 최소 단위이다. 예를 들어 1+1=21 + 1 = 20+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)이

P true∈ΓΓ⊢P truehyp \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \cfrac {\tJ P \in \Gamma} {\Gamma \vdash \tJ P} \text{hyp} \end{equation}

이다. 이 규칙 표현은 줄 위의 것들을 전제(premise)로 줄 밑의 결론(conclusion)을 추론(infer)해 낼 수 있음을 말한다. 전제에 쓰여있는 P true∈ΓP\ \texttt{true} \in \GammaP trueP\ \texttt{true}가 가정의 나열 Γ\Gamma에 포함되어 있음을 말한다. 또한 줄 옆에 써져있는 "hyp"는 이 추론 규칙(inference rule)의 이름으로서 가정(hypothesis)을 사용한다는 것을 나타낸다.

이런 추론 규칙을 명제를 만드는 각각의 방법에 대해 정의할 수 있다. 참 명제 ⊤\top에 대해서는

 Γ⊢⊤ trueI⊤ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \cfrac {\ } {\Gamma \vdash \tJ \top} \text{I}\top \end{equation}

라는 규칙을 줄 수 있다. 이 규칙 I⊤\text{I}\top은 어떤 Γ\Gamma를 가정하든 참 명제는 참이라고 판단할 수 있다는 규칙이다. 여기서 이름의 I\text{I}는 참 명제를 결론에 소개(Introduce)하는 규칙이라는 뜻이다. 대칭적으로 거짓 명제에 대해서는

Γ⊢⊥ trueΓ⊢P trueE⊥ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \cfrac {\Gamma \vdash \tJ \bot} {\Gamma \vdash \tJ P} \text{E}\bot \end{equation}

라는 규칙을 줄 수 있다. 이 규칙 E⊥\text{E}\bot은 어떤 가정들 Γ\Gamma로 부터 거짓 명제가 참이라고 판단했다면 그 가정들 아래에서는 아무 명제나 참이라고 판단할 수 있다는 규칙이다[2]. 여기서 이름의 E\text{E}는 우리가 거짓 명제가 참임을 결론으로 가지는 전제에 도달했을 때 그 결론을 제거(Eliminate)하여 다른 결론에 도달할 수 있도록 하는 규칙이라는 뜻이다.

⊤\top⊥\bot을 제외한 명제를 만드는 방법들은 이 두 종류의 규칙(I\text{I} 규칙과 E\text{E} 규칙)을 모두 가진다. P∧QP \land Q의 경우는 다음과 같다.

Γ⊢P trueΓ⊢Q trueΓ⊢P∧Q trueI∧Γ⊢P∧Q trueΓ,P true,Q true⊢R trueΓ⊢R trueE∧ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \cfrac {\Gamma \vdash \tJ P \qquad \Gamma \vdash \tJ Q} {\Gamma \vdash \tJ{P \land Q}} \text{I}\land \qquad \cfrac {\Gamma \vdash \tJ{P \land Q} \qquad \Gamma, \tJ P, \tJ Q \vdash \tJ R} {\Gamma \vdash \tJ R} \text{E}\land \end{equation}

I∧\text{I}\landΓ\Gamma를 가정하여 PPQQ 각각이 참이라 판단했다면 같은 가정에서 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 QP→QP \to Q를 위한 규칙도 나열하면 다음과 같다.

Γ⊢P trueΓ⊢P∨Q trueI∨1Γ⊢Q trueΓ⊢P∨Q trueI∨2Γ⊢P∨Q trueΓ,P true⊢R trueΓ,Q true⊢R trueΓ⊢R trueE∨Γ,P true⊢Q trueΓ⊢P→Q trueI→Γ⊢P→Q trueΓ⊢P trueΓ⊢Q trueE→ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \begin{array}{c} \cfrac {\Gamma \vdash \tJ P} {\Gamma \vdash \tJ{P \lor Q}} \text{I}\lor_1 \qquad \cfrac {\Gamma \vdash \tJ Q} {\Gamma \vdash \tJ{P \lor Q}} \text{I}\lor_2 \newline\newline \cfrac {\Gamma \vdash \tJ{P \lor Q} \qquad \Gamma, \tJ P \vdash \tJ R \qquad \Gamma, \tJ Q \vdash \tJ R} {\Gamma \vdash \tJ R} \text{E}\lor \newline\newline\newline \cfrac {\Gamma, \tJ P \vdash \tJ Q} {\Gamma \vdash \tJ{P \to Q}} \text{I}\to \qquad \cfrac {\Gamma \vdash \tJ{P \to Q} \qquad \Gamma \vdash \tJ P} {\Gamma \vdash \tJ Q} \text{E}\to \end{array} \end{equation}

여기서 I∨1\text{I}\lor_1I∨2\text{I}\lor_2PPQQ 중 하나가 참이라고 판단했다면 P∨QP \lor Q가 참이라고 판단할 수 있다는 규칙이며 E∨\text{E}\lorP∨QP \lor Q가 참이라고 판단할 수 있고 PP가 참이라고 가정하든 QQ가 참이라고 가정하든 RR이 참이라고 판단할 수 있다면 그 가정 없이 RR이 참이라고 판단할 수 있다는 규칙이다. I→\text{I}\toPP가 참임을 가정해서 QQ가 참이라고 판단할 수 있다면 P→QP \to Q 또한 참이라고 판단할 수 있다는 규칙이며 E→\text{E}\toP→QP \to Q가 참이라고 판단할 수 있고 PP가 참이라고 판단할 수 있다면 QQ가 참이라고 판단할 수 있다는 규칙이다. 이렇게 소개 규칙과 제거 규칙의 쌍으로 논리적 증명을 하는 방식을 "자연 연역"("Natural Deduction")이라고 한다.

자연 연역으로 단순한 논리적 증명을 하는 방법의 예시를 들어보겠다. 어떤 단위 명제 AA, BB, CC에 대해서 A→BA \to BB→CB \to C가 참이라고 판단했다면 A→CA \to C 또한 참이라 판단할 수 있다. 이 증명은 다음과 같다. 우선 A→BA \to B 그리고 B→CB \to CAA가 참이라고 판단했을 때 BB가 참이라고 다음과 같이 판단할 수 있다.

A→B true∈(A→B true,B→C true,A true)A→B true,B→C true,A true⊢A→B truehypA true∈(A→B true,B→C true,A true)A→B true,B→C true,A true⊢A truehypA→B true,B→C true,A true⊢B trueE→ \global\def\tJ#1{#1\ \texttt{true}} \global\def\MyGamma{\tJ{A \to B}, \tJ{B \to C}, \tJ A} \begin{equation} \begin{array}{c} \cfrac {\cfrac {\tJ{A \to B} \in (\MyGamma)} {\MyGamma \vdash \tJ{A \to B}} \text{hyp} \qquad \cfrac {\tJ A \in (\MyGamma)} {\MyGamma \vdash \tJ A} \text{hyp}} {\MyGamma \vdash \tJ B} \text{E}\to \end{array} \end{equation}

이를 써서 다음과 같이 증명을 끝낼 수 있다.

B→C true∈(A→B true,B→C true,A true)A→B true,B→C true,A true⊢B→C truehypA→B true,B→C true,A true⊢B true⏞위의증명A→B true,B→C true,A true⊢C trueE→A→B true,B→C true⊢A→C trueI→ \global\def\tJ#1{#1\ \texttt{true}} \global\def\MyGamma{\tJ{A \to B}, \tJ{B \to C}, \tJ A} \begin{equation} \begin{array}{c} \cfrac {\cfrac {\cfrac {\tJ{B \to C} \in (\MyGamma)} {\MyGamma \vdash \tJ{B \to C}} \text{hyp} \qquad \raisebox {-0.65em} {\(\overbrace {\MyGamma \vdash \tJ B} ^{위의 증명}\)}} {\MyGamma \vdash \tJ C} \text{E}\to} {\tJ{A \to B}, \tJ{B \to C} \vdash \tJ{A \to C}} \text{I}\to \end{array} \end{equation}

따라서 논리적으로 A→BA \to B 그리고 B→CB \to C가 참임을 가정했을 때 A→CA \to C가 참이라고 판단할 수 있다.

자연 연역과 함수형 언어

혹자는 위 규칙들에서 미묘한 친숙함을 발견했을지도 모른다. 이를 좀 더 구체적으로 살펴보기 위해 →\to의 규칙들을 다시 살펴보자.

Γ,P true⊢Q trueΓ⊢P→Q trueI→Γ⊢P→Q trueΓ⊢P trueΓ⊢Q trueE→ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \cfrac {\Gamma, \tJ P \vdash \tJ Q} {\Gamma \vdash \tJ{P \to Q}} \text{I}\to \qquad \cfrac {\Gamma \vdash \tJ{P \to Q} \qquad \Gamma \vdash \tJ P} {\Gamma \vdash \tJ Q} \text{E}\to \end{equation}

여기에 약간의 조작을 가해보자. 우선 Γ\Gamma를 단순한 판단의 나열로 취급하는 대신 이름 붙은 판단들로 취급하자. 예를 들어 위의 P trueP\ \texttt{true} 라는 판단에 xx라는 이름을 붙이면 다음과 같은 규칙을 얻는다.

Γ,x:P true⊢Q trueΓ⊢P→Q trueI→Γ⊢P→Q trueΓ⊢P trueΓ⊢Q trueE→ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \cfrac {\Gamma, x {:} \tJ P \vdash \tJ Q} {\Gamma \vdash \tJ{P \to Q}} \text{I}\to \qquad \cfrac {\Gamma \vdash \tJ{P \to Q} \qquad \Gamma \vdash \tJ P} {\Gamma \vdash \tJ Q} \text{E}\to \end{equation}

더욱이 각각의 규칙들에 지금까지의 증명을 나타내는 항들을 더해보도록 하겠다. 예를 들어 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으로 나타내도록 하자. 이 항들을 판단에 끼워넣으면 다음과 같이 규칙을 바꿔 쓸 수 있다.

Γ,x:P true⊢M:Q trueΓ⊢fun (x:P)⇒M:P→Q trueI→Γ⊢M:P→Q trueΓ⊢N:P trueΓ⊢M N:Q trueE→ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \cfrac {\Gamma, x {:} \tJ P \vdash M \mathbin{:} \tJ Q} {\Gamma \vdash \mathtt{fun}\ (x \mathbin{:} P) \Rightarrow M \mathbin{:} \tJ{P \to Q}} \text{I}\to \qquad \cfrac {\Gamma \vdash M \mathbin{:} \tJ{P \to Q} \qquad \Gamma \vdash N \mathbin{:} \tJ P} {\Gamma \vdash M\ N \mathbin{:} \tJ Q} \text{E}\to \end{equation}

이는 (단순한) 함수형 언어의 형 검사 규칙과 동일하다! 함수 fun (x : P) => M이 형 P -> Q를 가지는지 검사하고 싶을 때는 x : P를 가정한 뒤 M이 형 Q를 가지는지 검사하면 된다. 또한 함수 M을 인자 N에 적용하는 것이 형 Q를 가지는지 알고 싶다면 함수 M이 형 P -> Q를 가질 때 인자 N이 형 P를 가지는지 검사하면 된다. 마찬가지 변화를 다음과 같이 모든 추론 규칙들에 적용할 수 있다.

x:P true∈ΓΓ⊢x:P truehyp Γ⊢unit:⊤ trueI⊤Γ⊢M:⊥ trueΓ⊢case M of {}:P trueE⊥Γ⊢M:P trueΓ⊢N:Q trueΓ⊢(M,N):P∧Q trueI∧Γ⊢M:P∧Q trueΓ,x:P true,y:Q true⊢N:R trueΓ⊢case M of (x,y)⇒N:R trueE∧Γ⊢M:P trueΓ⊢Left M:P∨Q trueI∨1Γ⊢M:Q trueΓ⊢Right M:P∨Q trueI∨2Γ⊢M:P∨Q trueΓ,x1:P true⊢N1:R trueΓ,x2:Q true⊢N2:R trueΓ⊢case M of Left x1⇒N1 ∣ Right x2⇒N2:R trueE∨ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \begin{array}{c} \cfrac {x{:}\tJ P \in \Gamma} {\Gamma \vdash x \mathbin{:} \tJ P} \text{hyp} \newline\newline\newline \cfrac {\ } {\Gamma \vdash \mathtt{unit} \mathbin{:} \tJ \top} \text{I}\top \qquad \cfrac {\Gamma \vdash M \mathbin{:} \tJ \bot} {\Gamma \vdash \mathtt{case}\ M\ \mathtt{of}\ \{\} \mathbin{:} \tJ P} \text{E}\bot \newline\newline\newline\newline \cfrac {\Gamma \vdash M \mathbin{:} \tJ P \qquad \Gamma \vdash N \mathbin{:} \tJ Q} {\Gamma \vdash (M, N) \mathbin{:} \tJ{P \land Q}} \text{I}\land \newline\newline \cfrac {\Gamma \vdash M \mathbin{:} \tJ{P \land Q} \qquad \Gamma, x {:} \tJ P, y {:} \tJ Q \vdash N {:} \tJ R} {\Gamma \vdash \mathtt{case}\ M\ \mathtt{of}\ (x, y) \Rightarrow N \mathbin{:} \tJ R} \text{E}\land \newline\newline\newline\newline \cfrac {\Gamma \vdash M \mathbin{:} \tJ P} {\Gamma \vdash \mathtt{Left}\ M \mathbin{:} \tJ{P \lor Q}} \text{I}\lor_1 \qquad \cfrac {\Gamma \vdash M \mathbin{:} \tJ Q} {\Gamma \vdash \mathtt{Right}\ M \mathbin{:} \tJ{P \lor Q}} \text{I}\lor_2 \newline\newline \cfrac {\Gamma \vdash M \mathbin{:} \tJ{P \lor Q} \qquad \Gamma, x_1{:}\tJ P \vdash N_1 \mathbin{:} \tJ R \qquad \Gamma, x_2{:}\tJ Q \vdash N_2 \mathbin{:} \tJ R} {\Gamma \vdash \mathtt{case}\ M\ \mathtt{of}\ \mathtt{Left}\ x_1 \Rightarrow N_1\ |\ \mathtt{Right}\ x_2 \Rightarrow N_2 \mathbin{:} \tJ R} \text{E}\lor \end{array} \end{equation}

⊤\top은 단위(unit) 자료형에 대응되며 ⊥\bot은 빈 자료형에 대응되고 P∧QP \land QP 형과 Q 형의 쌍(pair) 자료형에 대응된다. 같은 방식으로 P∨QP \lor QP 형과 Q 형의 합(sum) 자료형[3]에 대응된다. 항에서부터 각각의 추론 규칙이 (I\texttt{I} 규칙의 경우) 각 자료형의 생성자(constructor)나 (E\texttt{E} 규칙의 경우) 패턴 맞추기(pattern matching)의 형 검사 규칙에 대응된다는 것을 볼 수 있을 것이다. 이를 처음 구체화시킨 두 사람의 이름을 따 이 대응을 "커리-하워드 대응"("Curry-Howard correspondence")이라고 부른다. 이 대응이 발견됨으로써 논리적인 사고와 프로그래밍 언어의 이해 간에 중요한 연결고리가 생겼고 이는 현재에도 프로그래밍 언어의 발전과 논리학의 발전 양측에 모두 지대한 영향을 끼치고 있다.

이 대응을 따랐을 때 어떤 판단을 증명한다는 것은 그 판단의 형을 가지는 프로그램(program)을 짜는 것과 동일하다. 이를 보다 구체적으로 이해하기 위해 앞서의 예시 증명을 다시 반복해보자. 증명의 너비를 줄이기 위해 Γ\Gammax: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 true∈ΓΓ⊢x:A→B truehypz:A true∈ΓΓ⊢z:A truehypΓ⊢x z:B trueE→ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \begin{array}{c} \cfrac {\cfrac {x{:}\tJ{A \to B} \in \Gamma} {\Gamma \vdash x \mathbin{:} \tJ{A \to B}} \text{hyp} \qquad \cfrac {z{:}\tJ A \in \Gamma} {\Gamma \vdash z \mathbin{:} \tJ A} \text{hyp}} {\Gamma \vdash x\ z \mathbin{:} \tJ B} \text{E}\to \end{array} \end{equation}

즉 함수 x : A -> B와 인자 z : A가 있을 때 함수 적용 x zB trueB\ \texttt{true}의 증명이다. 이어서,

y:B→C true∈ΓΓ⊢y:B→C truehypΓ⊢x z:B true⏞위의증명Γ⊢y (x z):C trueE→x:A→B true,y:B→C true⊢fun (z:A)⇒y (x z):A→C trueI→ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \begin{array}{c} \cfrac {\cfrac {\cfrac {y{:} \tJ{B \to C} \in \Gamma} {\Gamma \vdash y \mathbin{:} \tJ{B \to C}} \text{hyp} \qquad \raisebox {-0.65em} {\(\overbrace {\Gamma \vdash x\ z \mathbin{:} \tJ B} ^{위의 증명}\)}} {\Gamma \vdash y\ (x\ z) \mathbin{:} \tJ C} \text{E}\to} {x{:}\tJ{A \to B}, y{:}\tJ{B \to C} \vdash \mathtt{fun}\ (z \mathbin{:} A) \Rightarrow y\ (x\ z) \mathbin{:} \tJ{A \to C}} \text{I}\to \end{array} \end{equation}

따라서 fun (z : A) => y (x z)x : A -> By : 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의 규칙에 대해 살펴보자.

Γ⊢P trueΓ⊢Q trueΓ⊢P∧Q trueR∧Γ,P true,Q true⊢R trueΓ,P∧Q true⊢R trueL∧ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \begin{array}{c} \cfrac {\Gamma \vdash \tJ P \qquad \Gamma \vdash \tJ Q} {\Gamma \vdash \tJ{P \land Q}} \text{R}\land \qquad \cfrac {\Gamma, \tJ P, \tJ Q \vdash \tJ R} {\Gamma, \tJ{P \land Q} \vdash \tJ R} \text{L}\land \end{array} \end{equation}

차이점을 눈치챘는가? 크게 두 차이점이 있다.

  1. 규칙의 이름이 다르다. 😅
  2. 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}\landL∧\text{L}\land의 꼴의 차이는 이 접근법의 차이에서부터 자연스럽게 따라나온 것이다.

명제를 만드는 다른 방법들에 있어서도 논건 대수의 방식을 따라서 추론 규칙을 마련해 줄 수 있다.

 Γ⊢⊤ trueR⊤ Γ,⊥ true⊢P trueL⊥Γ⊢P trueΓ⊢P∨Q trueR∨1Γ⊢Q trueΓ⊢P∨Q trueR∨2Γ,P true⊢R trueΓ,Q true⊢R trueΓ,P∨Q true⊢R trueL∨Γ,P true⊢Q trueΓ⊢P→Q trueR→Γ⊢P trueΓ,Q true⊢R trueΓ,P→Q true⊢R trueL→ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \begin{array}{c} \cfrac {\ } {\Gamma \vdash \tJ \top} \text{R}\top \qquad \cfrac {\ } {\Gamma, \tJ \bot \vdash \tJ P} \text{L}\bot \newline\newline\newline \cfrac {\Gamma \vdash \tJ P} {\Gamma \vdash \tJ{P \lor Q}} \text{R}\lor_1 \qquad \cfrac {\Gamma \vdash \tJ Q} {\Gamma \vdash \tJ{P \lor Q}} \text{R}\lor_2 \newline\newline \cfrac {\Gamma, \tJ P \vdash \tJ R \qquad \Gamma, \tJ Q \vdash \tJ R} {\Gamma, \tJ{P \lor Q} \vdash \tJ R} \text{L}\lor \newline\newline\newline \cfrac {\Gamma, \tJ P \vdash \tJ Q} {\Gamma \vdash \tJ{P \to Q}} \text{R}\to \qquad \cfrac {\Gamma \vdash \tJ P \qquad \Gamma, \tJ Q \vdash \tJ R} {\Gamma, \tJ{P \to Q} \vdash \tJ R} \text{L}\to \end{array} \end{equation}

그리고 논건 대수가 자연 연역과 동등함을 (비교적) 쉽게 보이기 위해서는 명제를 만드는 각 방식의 R\text{R}L\text{L} 규칙에 더해 다음의 두 규칙을 추가해야한다.

P true∈ΓΓ⊢P trueinitΓ⊢P trueΓ,P true⊢Q trueΓ⊢Q truecut \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \begin{array}{c} \cfrac {\tJ P \in \Gamma} {\Gamma \vdash \tJ P} \text{init} \qquad \cfrac {\Gamma \vdash \tJ P \qquad \Gamma, \tJ P \vdash \tJ Q} {\Gamma \vdash \tJ Q} \text{cut} \end{array} \end{equation}

여기서 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}\landE∧\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}를 증명하지 못한다.

증명은 다음의 네 문장이면 충분하다.

  1. 귀결이 ⊥ true\bot\ \texttt{true}R\text{R} 규칙이 존재하지 않기 때문에 어떤 R\text{R} 규칙도 쓸 수 없다.
  2. 가정이 없기 때문에 어떤 L\text{L} 규칙도 쓸 수 없다.
  3. 마찬가지로 가정이 없기 때문에 init\text{init} 규칙을 쓸 수 없다.
  4. 따라서 어떤 규칙도 판단 ⊢⊥ true\vdash \bot\ \texttt{true}을 결론으로 주지 않는다.

앞서 이 글의 서두에서 논건 대수의 일관성을 보이기 위해 "약간의 부정행위"를 저지를 것이라고 말했다. 오해가 없도록 명시하자면 위의 증명은 cut\text{cut}을 제거한 논건 대수에 있어서는 문제가 없다. 부정행위라고 부를 만한 부분은 바로 cut\text{cut} 제거 정리의 증명이 더 어렵다는 점이다. 이 증명은 단순화하기 쉽지 않기 때문에 글에서 직접적으로 다루지는 않을 것이고, 이것이 바로 서두에서 언급한 "부정행위"이다. 다만 이 어려운 증명도 수학적 귀납법(Induction) 이상의 지식은 필요로 하지 않기 때문에, 시도해보고자 하는 독자가 있다면 직접 시도해 볼 수 있을 것이다[5].

논건 대수의 소개를 앞서의 예시 증명을 논건 대수에서 반복하는 것으로 마치도록 하자. 우선 A→BA \to BAA가 참이라고 판단했을 때 BB가 참이라고 다음과 같이 판단할 수 있다.

A true∈(A true)A true⊢A trueinitB true∈(A true,B true)A true,B true⊢B trueinitA→B true,A true⊢B trueL→ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \begin{array}{c} \cfrac {\cfrac {\tJ A \in (\tJ A)} {\tJ A \vdash \tJ A} \text{init} \qquad \cfrac {\tJ B \in (\tJ A, \tJ B)} {\tJ A, \tJ B \vdash \tJ B} \text{init}} {\tJ{A \to B}, \tJ A \vdash \tJ B} \text{L}\to \end{array} \end{equation}

이를 써서 다음과 같이 증명을 끝낼 수 있다.

A→B true,A true⊢B true⏞위의증명C true∈(A→B true,A true,C true)A→B true,A true,C true⊢C trueinitA→B true,B→C true,A true⊢C trueL→A→B true,B→C true⊢A→C trueR→ \global\def\tJ#1{#1\ \texttt{true}} \begin{equation} \begin{array}{c} \cfrac {\cfrac {\raisebox {-0.65em} {\(\overbrace {\tJ{A \to B}, \tJ A \vdash \tJ B} ^{위의 증명}\)} \qquad \cfrac {\tJ C \in (\tJ{A \to B}, \tJ A, \tJ C)} {\tJ{A \to B}, \tJ A, \tJ C \vdash \tJ C} \text{init}} {\tJ{A \to B}, \tJ{B \to C}, \tJ A \vdash \tJ C} \text{L}\to} {\tJ{A \to B}, \tJ{B \to C} \vdash \tJ{A \to C}} \text{R}\to \end{array} \end{equation}

앞서의 자연 연역 증명의 구조가 크게 다름에 주의해서 논건 대수 증명의 구조를 이해해보도록 하자. 앞서의 자연 연역 증명은

hyp규칙E규칙↓E규칙↑I규칙결론E규칙 \begin{array}{c} \texttt{hyp} 규칙 \qquad \phantom{\texttt{E} 규칙}\\ \downarrow \qquad \texttt{E} 규칙\\ \uparrow \qquad \texttt{I} 규칙\\ 결론 \qquad \phantom{\texttt{E} 규칙} \end{array}

의 구조를 가지고 있다. 즉 결론에 I\texttt{I} 규칙을 적용하고 어떤 가정에 E\texttt{E} 규칙을 적용해서 이 둘이 만나는 지점을 찾는 것이 증명의 구조이다. 이와는 다르게 논건 대수의 증명은

L규칙init규칙R규칙L규칙↓init규칙↓R규칙결론 \begin{array}{c} \phantom{\texttt{L} 규칙} \qquad \texttt{init} 규칙 \qquad \phantom{\texttt{R} 규칙}\\ \texttt{L} 규칙 \qquad \downarrow \phantom{\texttt{init} 규칙} \downarrow \qquad \texttt{R} 규칙\\ 결론 \end{array}

와 같이 (여러 개의) 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 편에서 다룰 본격적인 논리와 자료 표현의 관계에 대해 흥미를 불러일으켰기를 바라며 이만 마치도록 하겠다.


  1. 이 중 두번째 단위 명제는 일반적인 자연수 체계에서 거짓이나, 명제의 정의 자체는 명제가 참인지 거짓인지에 대해 논하지 않는다는 것을 강조하기 위해 단위 명제에 포함하였다. ↩︎

  2. 거짓도 참이라면 대체 무엇이 참이 아니겠는가? ↩︎

  3. 이는 Rust나 OCaml의 Result 형 혹은 Haskell의 Either형과 같은 형이다. ↩︎

  4. 전건(前件, antecedent)과 후건(後件, succedent)을 개별적으로 다룰 수 있다는 의미에서 "Sequent Calculus"를 논건 대수(論件 代數)라고 번역하였다. 보다 일반적으로 "시퀀트 대수"라는 표현이 쓰이나, 이는 "시퀀트"가 의미하는 바가 "논리에서 다루는 물건/사건/…\ldots"이라는 표현보다 직관성이 떨어진다고 보아 재번역을 하였다. ↩︎

  5. 증명에 대한 구체적인 질문이 있다면 답글을 남겨주기를 바란다. ↩︎

Read more →
9

@bglbgl gwyng vscode 쓰시죠? 예전 기억을 더듬어보면nix-ld만 잘 설정해서 필요하면 extension들을 ad-hoc하게 깔아서 쓰는덴 큰 문제가 없었습니다. (제 경험상 vscode-fhs는 비추합니다) 저는 그러다 한번씩 pkgs/applications/editors/vscode/extensions/update_installed_exts.sh 스크립트를 이용해서 깔려있는 extension들을 nix-expression으로 뽑아서 vscode-with-extensions로 다시 빌드하곤 했습니다.

2
2
0

I'm exploring a new idea called FediOTP (codename): an authentication system that uses DMs to deliver one-time passwords, allowing any account to authenticate with web services. Unlike current solutions that rely on specific APIs (, ), this would work with any ActivityPub-compatible server, increasing interoperability across the fediverse. Would love to hear your thoughts on potential challenges or use cases for this approach.

1
5
0

많은 파일을 대상으로 텍스트를 대량 치환해야 할 일이 꽤 있는데, 파일 탐색과 치환을 인터랙티브하게 수행할 수 있는 툴이 없어서 ised(interactive sed)를 만들었다. 월요일부터 개밥먹기 할 예정. github.com/parksb/ised

3

@zust 플러그인은 오히려 Yarn이 잘 만든 소프트웨어라는 생각을 하게 된 계기예요 ㅎㅎ 저는 '공격적인 수정' 중 하나가 PnP라고 생각하는데요, 최근에 겪은 문제로는...

(1) Node v22.12에서 require(esm)이 가능해졌음에도 PnP는 이를 제대로 지원하지 못했습니다. (github.com/yarnpkg/berry/issue)

(2) Go로 재작성하고 있는 타입스크립트도 PnP와의 호환이 문제가 됩니다. (github.com/microsoft/typescrip)

Yarn만의 문제는 아니고, 호환성과 혁신성을 트레이드오프할 때 일어날 수 있는 일인 것 같습니다.

0

bgl gwyng shared the below article:

간단한 rpmbuild의 사용과 private rpm package 배포

Perlmint @perlmint@hackers.pub

마지못해 패키지를 만들어야 할 것 같은 사람을 위한 설명입니다. 제대로된 패키지를 만들고 싶은 경우에는 부족한 점이 많습니다.

대부분의 경우에는 프로그램을 직접 소스에서 빌드하는 일도 적고, 그걸 시스템 전역에 설치하는 일도 흔치는 않을 것입니다. 좋은 패키지매니저와 관리가 잘되는 패키지 저장소들을 두고 아주 가끔은 직접 빌드를 할 일이 생기고, 흔치 않게 시스템 전역에 설치할 일이 생길 수 있습니다. 어지간한 프로그램들은 요즈음의 장비에서는 별 불만 없이 빌드 할 만한 시간이 소요되나, 컴파일러처럼 한번 빌드했으면 다시는 하고 싶지 않은 프로그램을 다시 설치해야하는 경우도 있을 수 있습니다. 하필 이런 프로그램들은 결과물도 덩치가 매우 큽니다. 이럴 때는 최대한 간단하고 필요한 항목만 패키지에 넣어서 만들어두고 다시 활용하면 좋을 것이기에 이런 경우를 위한 rpmbuild에 대한 나름 최소한의 사용 방법을 정리해봅니다.

rpmbuild

rpmbuild는 rpm-build 패키지로 설치가 가능하며, 나름 단순하게 rpm으로 패키징을 할 수 있는 유틸리티입니다. spec파일에 패키지 정보, 빌드 명령, 설치 명령, 패키지가 포함해야 할 파일 목록을 작성해서 rpmbuild에 입력으로 넣어주면 빌드부터 시작해서 rpm패키지를 만들어줍니다. native 프로그램의 경우 디버그 심볼을 알아서 분리해서 별도의 패키지로 만들어주고, 필요한 의존성도 추정해서 명시해줍니다. 또한, 필요한 경우 하나의 spec 명세로 연관된 서브 패키지도(ex. 실행파일 패키지인 curl과 라이브러리 패키지 libcurl, 라이브러리를 사용하기 위한 개발 패키지 libcurl-devel) 같이 만들 수 있습니다.

작업 환경

rpmbuild는 기본으로 ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS,BUILDROOT}의 경로에서 동작하며 각 경로의 용도는 다음과 같습니다.

  • SOURCES에는 압축된 소스코드가 위치합니다.
  • SPECS에는 패키지 정의인 spec파일을 둡니다.
  • BUILD밑에서 빌드 작업이 진행됩니다.
  • RPMS에 바이너리 rpm결과물이 생성됩니다.
  • SRPMS에는 소스 rpm결과물이 생성됩니다.
  • BUILDROOT는 패키징 하기 위해 빌드 결과물을 모으는 경로입니다.

spec파일

spec파일은 패키지를 어떻게 빌드하고 어떤 항목들이 패키지에 포함될지, 패키지의 이름, 설명 및 의존성 등의 메타데이터, 패키지 설치, 삭제시의 스크립트를 정의할 수 있습니다. 보통 시작 부분에는 메타데이터 정의로 시작하며, 다음과 같은 기본적인 형태를 취합니다. 나름 단순하게 만든 python을 위한 spec을 예시로 들어보겠습니다.

Summary: Python %{version}
Name: python-alternative
Version: %{version}
Release: 1%{?dist}
Obsoletes: %{name} <= %{version}
Provides: %{name} = %{version}
URL: https://www.python.org
Requires: libffi openssl
AutoReq: no
License: PSFL

Source: https://www.python.org/ftp/python/%{version}/Python-%{version}.tgz

BuildRequires: libffi-devel openssl-devel
BuiltRoot: %{_tmppath}/%{name}-%{version}-%{release}-root

%define major_version %(echo "%{version}" | sed -E 's/^([0-9]+)\\..+/\1/' | tr -d)
%define minor_version %(echo "%{version}" | sed -E 's/^[0-9]+\\.([0-9]+)\\..+/\1/' | tr -d)

%description
Python

%package devel
Summary: python development files
Requires: %{name} = %{version}-%{release}

%description devel
Python development package

%prep
%setup -q -n Python-%{version}

%build
./configure --prefix=%{_prefix}

%install
%{__make} altinstall DESTDIR=%{buildroot}
%{__ln_s} -f %{_bindir}/python%{major_version}.%{minor_version} %{buildroot}/%{_bindir}/python%{major_version}

%clean
%{__rm} -rf %{buildroot}

%files
%{_bindir}/python*
%exclude %{_bindir}/idle*
%{_bindir}/pip*
%{_bindir}/pydoc*
%exclude %{_bindir}/2to3*
%{_libdir}/libpython*
%{_prefix}/lib/libpython*
%{_prefix}/lib/python*
%{_mandir}/man1/python*

%files devel
%{_includedir}/python*
%{_prefix}/lib/pkgconfig/python*

%로 매크로를 사용할 수 있으며, %package, %description, %files 같은 매크로는 인자를 주어서 서브 패키지를 정의하는데도 쓸 수 있습니다. 앞선 예제처럼 devel 이라고작성하면 메인 패키지이름 뒤에 붙여서 python-alternative-devel가 되며, curl - libcurl과 같은 경우에는 메인의 이름은 curl이고, 딸린 패키지를 정의할 때는 %package -n libcurl과 같이 -n옵션을 추가해서 지정할 수 있습니다. 몇몇 매크로는 단계를 정의하는 것과 같은 동작을 하며 다음과 같습니다.

%package

유사성을 보면 spec파일의 맨 첫부분은 메인 패키지의 %package에 해당하는 것이 아닌가 싶습니다. <Key>: <Value>의 형태로 메타정보를 작성합니다. 대부분은 Key를 보면 무슨 값인지 추측 할 만합니다. 나중에 설명할 %files에서 나열한 파일을 rpmbuild가 분석하여 자동으로 패키지가 필요로 하는 의존성을 추정해서 추가 해 줍니다. python 스크립트, perl 스크립트, native 실행파일 등을 분석해서 알아서 추가해주는 것 같은데, 경우에 따라서는 틀린 의존성을 추가해주기도 합니다. 이 때는 AutoReq: no를 설정하여 자동 의존성 추가를 막을 수 있습니다. 이 python-alternative 패키지는 /usr/local/bin/python%{version}을 설치하는데 아마도 같이 포함되는 python 스크립트에 의해서 /bin/python을 의존성으로 추정하여 요구합니다. 패키지 스스로가 제공하는 의존성은 미리 설치 되어있기를 요구하지 않게 동작하는 것 같으니 보통은 문제가 없습니다만, 이 경우에는 스스로 제공을 하지 않기 때문에 python을 설치하기 위해서 python이 필요한 경우가 발생하므로 AutoReq를 껐습니다.

%prep

준비단계로 소스코드의 압축을 해제하고 필요한경우 패치를 적용합니다. %setup 매크로를 이 안에서 보통 사용하며, %setupSource에 명시된 파일명의 압축 파일을 SOURCES 밑에서 찾아서 압축을 풉니다. 그리고 동일한 이름의 디렉토리로 이동을 합니다. 앞선 예제에서는 SOURCES/Python-%{version}.tgz의 압축을 풀고 Python-%{version}으로 이동을 합니다. 패치가 필요한 경우 보통 이 뒤에 패치를 적용하는 명령들을 추가 합니다.

%build

설정, 컴파일 등을 수행하는 단계입니다. 이곳에서 자주 하는 매크로로 %configure, %make_build 등이 있습니다. %configure는 configure를 prefix 및 기타 몇가지 일반적으로 쓰이는 옵션을 추가하여 실행해주며, %make_buildmake와 비슷하게 모든 타겟을 빌드 합니다. 예제에서는 둘다 안쓰고 있고, 심지어 실제 빌드는 안하는데 어쨌든 이후의 %install까지 지나고나서 빌드 결과물만 맞는 위치에 만들어지면 대충 패키지를 만드는데는 별 문제는 없는 것 같습니다.

%install

여기서 빌드 결과물을 설치하는 명령을 작성합니다. 일반적으로 %make_install을 사용하여 make install DESTDIR=%{buildroot}와 비슷한 명령을 수행하여 %{buildroot}밑에 빌드 결과물이 prefix를 유지하여 설치되게 합니다. 예제의 %{__ln_s} -f %{_bindir}/python%{major_version}.%{minor_version} %{buildroot}/%{_bindir}/python%{major_version}을 보면 추정 할 수 있듯이, 패키지에 포함시킬 파일들을 %{buildroot}밑에 생성을 하면 되며, 추가적인 심볼릭 링크는 패키지를 빌드하는 시점에는 존재하지 않지만, 패키지를 설치하게되면 존재하게 될 %{_bindir}/python%{major_version}.%{minor_version}를 향하는 것을 %{buildroot} 밑인 %{buildroot}/%{_bindir}/python%{major_version}에 만듭니다.

%files

패키지에 포함될 파일 목록을 작성합니다. glob 양식으로 파일 목록을 작성할 수 있습니다. %{buildroot} 밑에 생성 되었지만 어느 %files에도 포함되지 않은 파일이 있는 경우에는 빌드를 실패합니다. 그러므로 %exclude를 사용해서 명시적으로 제외해줘야 합니다.

기타 매크로

rpmbuild에서는 기본으로 다양한 매크로를 제공하고 있습니다. --define "_libdir %{_prefix}/lib64"와 같은 옵션을 실행시에 주어서 실행시점에 매크로를 덮어 쓸 수도 있고, 앞선 spec파일 내의 %define major_version 와 같이 다른 매크로와 셸 명령을 활용하여 매크로를 정의 할 수도 있습니다. 원하는 동작을 안하는 것 같은 경우에는 --show-rc옵션을 사용하여 매크로가 어떻게 정의되어있는지 확인해 볼 수 있습니다.

빌드

rpmbuild의 매뉴얼을 보면 자세하게 나와있지만 가장 단순하게는 rpmbuild -bb <specfile>로 바이너리 패키지를 빌드할 수 있습다. 이 때, 압축된 소스코드는 미리 SOURCES밑에 두어야 합니다.

private rpm package 배포

직접 비공개 패키지 저장소 프로그램을 실행하여 제공하는 방법도 있겠지만, 최대한 간단하게 할 수 있는 방법으로, rpm관련 패키지 설치 명령이 입력으로 http등의 URL도 받는 것을 활용하여 적당한 장비에서 http로 서빙을 해주면 됩니다.

Read more →
4
5
9
0

Bootable 컨테이너 시대, 리눅스 테마 꾸미기의 즐거움
------------------------------
- *bootc와 부팅 가능한 컨테이너* 덕분에 테마 변경이 더 안전하고 관리 가능한 방식으로 가능해짐
-
/usr를 *컨테이너로 정의하고 롤백 가능* 하므로 시스템을 쉽게 실험하고 되돌릴 수 있음
-
ostree admin unlock 명령으로 재부팅 없이도 *일시적인 커스터마이징* 이 가능함
- Blue95 같은 프로젝트는…
------------------------------
https://news.hada.io/topic?id=20479&utm_source=googlechat&utm_medium=bot&utm_campaign=1834

0

3년차 웹 프런트엔드 개발자입니다. 잠시 10주 여름 방학 동안 계약직으로 일할 수 있는 직장을 찾고 있습니다. (6월 마지막 주부터 8월 마지막 주) http://frontend.moe/portfolio/

올해 2학기까지 수료하면 졸업 예정이라, 학부 졸업 이후 정규직 전환 조건으로도 희망하고 있습니다.

4

As someone who has developed several software implementations (Fedify, Hollo, BotKit, and Hackers' Pub), I believe one of the most frustrating features to implement in the is .

The challenges are numerous:

First, there's no standardization. ActivityPub specifications don't define how custom emoji should work, leading to inconsistent implementations across different servers like Mastodon and Misskey.

Rendering is particularly problematic. Emojis must display properly across different contexts (in text, as reactions, in emoji pickers) while maintaining quality at various sizes. Animated emojis add another layer of complexity.

Perhaps most concerning is the poor . Most implementations simply use the emoji code (like :party_blob:) as the alt text, which provides no meaningful information to screen reader users (in particular, non-English speakers) about what the emoji actually depicts or means.

What really dampens my motivation to implement this feature is knowing I'm investing significant effort into something that ultimately creates accessibility barriers. It's disheartening to work hard on a feature that excludes part of the community.

2
6
1
0

저도 두 가지 쟁점 모두 동의하는 편입니다. 그리고, 별개의 이야기입니다만, $가르칠 때에는 그냥 문법이라고 가르치는 게 학습자의 이해와 응용이 압도적으로 빠르고 좋았습니다.

"이건 여기서부터 뒤로는 다 괄호로 감싸겠다는 뜻이라고 생각하세요."

이러면 한 방에 설명이 끝나고, 필요성이나 편리성에 대해서도 알아서들 납득하는 것이죠. 연산자 우선순위나 좌결합 우결합 등은 그게 되고 나서 얘기하고요. 그러면 "아, 이게 그래서 이렇게 되는 거였군요?" 하면서, 훨씬 쉽게 이해합니다. 이걸 거꾸로 좌결합 우결합 어쩌고부터 가르치려고 하면 다들 꾸벅꾸벅 졸아요... ㅋㅋ ㅠㅠ

(결국 "모나드란 무엇인가"부터 배우면/가르치면 안 된다는 주장과 같은 맥락입니다.)



RE: https://hackers.pub/@bgl/01963c3b-98fa-7432-a62f-0d2dfc0691bf

5

미국인에게 미터법을 가르치는 방법

1. M16A4는 정확히 1미터입니다. 그게 아니어도 M16계열은 다 1미터에서 1cm이내로 만들어집니다.
2. 9mm총알의 탄피는 약 1mm가 추가되어 약 1cm가 됩니다.
3. 대부분의 경우 마약은 이미 그램/키로그램으로 잽니다. 생산/유통/소비과정에서 양을 틀리면 ㅈ되는 경우가 많아서 그런거 아닐까요;;

이것이

0

지금까지 Hackers' Pub은 반드시 이메일을 통해 로그인 링크를 수신하는 식으로만 로그인이 가능했는데, 사실은 많이 번거로웠죠?

이를 해결하기 위해 Hackers' Pub에 패스키 기능을 추가했습니다. 패스키 추가는 설정패스키 페이지에서 할 수 있으며, 패스키가 등록된 기기 및 브라우저에서는 로그인 페이지에서 자동적으로 패스키를 사용할 것인지 묻는 창이 뜨게 됩니다.

Hackers' Pub의 패스키 설정 페이지. 위쪽에는 패스키 등록을 위한 폼이, 아래쪽에는 등록된 패스키를 나열한 표가 보인다.Hackers' Pub의 로그인 페이지. 우측 상단에 패스키를 사용하여 로그인할 것인지 묻는 창이 보인다.
9
0
3
0

The Korean fediverse community is proposing to establish April 11th as “Fediverse Day” (聯合宇宙(연합우주)의 날). Since this idea emerged today in conversations among Korean fediverse users, they've suggested making today's date the official celebration going forward.

We're sharing this initiative with the broader fediverse community and would love to know if you'd support and participate in an annual Fediverse Day!

Excited to share that @pbzweihander쯔방 :yuri: :yurigarden: :garden: has created fediday.org—a new website dedicated to Fediverse Day! The site celebrates April 11th as Federated Fediverse Day, which originated from conversations in the Korean fediverse community. Visit the site to learn about the background of this initiative and discover other important dates in the fediverse calendar. If you'd like to add a special date, you can contribute through the GitHub repository!

3
1
0

쿠버네티스도 간단하게 쓸 수 있죠. 요즘 k3s 같은거 쓰면 구성도 쉽고, 사용도 그냥 kubectl apply -f deployment.yaml 하면 끝인데. 이렇게만 쓰면 도커컴포즈랑 그렇게 다르지 않습니다.

근데도 쿠버를 쓰지 말라는 이유는, '잘못 쓸 여지'가 많기 때문입니다

쿠버를 쓰다 보면, 괜히 GitOps 하고 싶어서 ArgoCD 깔고, 서비스 메시 한다고 Istio 깔고, prometheus 깔고, thanos 셋업하고, EFK 스택 만들고, 이러다보면 아무도 유지보수 못하는 쿠버네티스 클러스터가 완성됩니다. 아니면 옵스 엔지니어가 주 40시간 전체를 이거를 간신히 존속시키는데에만 다 쓰고 나머지 아무것도 못 합니다.

이런거 다 참을 수 있고 k3s로 깔고
kubectl apply -f 만 치고 살거면 쿠버 쓰셔도 됩니다.

첨부한 사진이 무슨 링크드인에 '2025년 쿠버네티스 표준 구성' 이라고 돌아다니던데, 제발 이러지 마세요.

도커컴포즈 쓰면 이런걸 아예 못 하게 되니까 오히려 장점인거죠. 잘못 쓸 여지가 없음.

2
0

bgl gwyng 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(nlog⁡n)O(n\log{n})같다.

등이 있다. 이렇게 어떤 두 대상이 같은지에 대해서 이야기를 하다보면 반대로 어떤 두 대상이 같지 않은지에 대해서도 이야기하게 된다. 즉,

  • x+4x + 422로 나눈 나머지는 x+1x + 122로 나눈 나머지와 같지 않다.
  • 연결 리스트(Linked List)와 배열(Array)은 같지 않다.
  • 함수 λ x→x\lambda\ x \to x와 정수 55같지 않다.

같은 것과 판정 문제(Decision Problem)

이제 컴퓨터 과학(Computer Science)과 프로그래밍(Programming)에 있어 자연스러운 의문은 "두 대상이 같은지 아닌지와 같은 답을 주는 알고리즘(Algorithm)이 있나?"일 것이다. 다시 말해서 두 대상 aabb를 입력으로 주었을 때

  • 알고리즘이 참 값(True\mathtt{True})을 준다면 aabb가 같고
  • 알고리즘이 거짓 값(False\mathtt{False})을 준다면 aabb가 같지 않은

알고리즘이 있는지 물어볼 수 있다. 이런 어떤 명제가 참인지 거짓인지 판정하는 알고리즘의 존재 여부에 대한 질문을 "판정 문제"("Decision Problem")라고 하며, 명제 PP에 대한 판정 문제에서 설명하는 알고리즘이 존재한다면 "PP는 판정 가능하다"("PP is decidable")고 한다. 즉, 앞의 질문은 "임의의 aabb에 대해 aabb가 같은지 판정 가능한가?"라는 질문과 같은 의미라고 할 수 있다.

이 질문에 대한 대답은 당연하게도 어떤 대상을 어떻게 비교하는지에 따라 달라진다. 예를 들어 우리가 32 비트(bit) 정수에 대해서만 이야기하고 있다면 "임의의 32 비트 정수 aabb에 대해 aabb가 각 비트별로 같은지 판정 가능한가?"라는 질문에 대한 답은 "그렇다"이다. 반면 우리가 비슷한 질문을 자연수를 받아 자연수를 내놓는 임의의 함수에 대해 던진다면 답은 "아니다"가 된다.[1]

그렇다면 어떤 대상의 어떤 비교에 대해 판정 문제를 물어보아야할까? 프로그래머(Programmer)로서 명백한 대답은 두 프로그램(Program)이 실행 결과에 있어서 같은지 보는 것일 것이다. 그러나 앞서 자연수를 받아 자연수를 내놓는 함수에 대해 말했던 것과 비슷하게 두 프로그램의 실행 결과를 완벽하게 비교하는 알고리즘은 존재하지않는다. 이는 우리가 두 프로그램의 같음을 판정하고 싶다면 그 같음을 비교하는 방법에 제약을 두어야 함을 말한다. 여기서는 다음의 두 제약을 대표로 설명할 것이다.

  1. 문법적 비교(Syntactic Comparison)
  2. β\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);

두 프로그램 모두 x5라는 값을 할당하고 5를 콘솔에 출력하나, 첫번째 프로그램은 = 5;를, 두번째 프로그램은 = 3 + 2을 사용하여 5를 할당하고 있기 때문에 문법적으로 다르다.

문법적 비교는 이렇게 문법만 보고서 쉽게 판정할 수 있다는 장점이 있으나, 두번째 예시처럼 쉽게 같은 행동을 함을 이해할 수 있는 프로그램에 대해서도 "같지 않음"이라는 결과를 준다는 단점을 가진다. 혹자는

3 + 2같은 계산은 그냥 한 다음에 비교하면 안돼? 컴파일러(Compiler)도 상수 전파(Constant Propagation) 최적화라던지로 3 + 25로 바꾸잖아?

라는 생각을 할 수도 있을 것이다. 이 제안을 반영한 방법이 바로 β\beta 동등성이다.

2. β\beta 동등성

바로 앞의 소절에서 단순 계산의 추가에 의해 같음같지 않음으로 변하는 것을 보았다. 이런 상황을 피하기 위해서는 같음을 평가할 때 프로그램의 실행을 고려하도록 만들어야 한다. 가장 대표적인, 대부분의 프로그래밍 언어(Programming Language)에 존재하는 프로그램의 실행은 함수 호출이다. 따라서 함수 호출을 고려한 같음의 비교는 f(c)와 함수 f의 몸체 b 안에서 인자 xc로 치환한 것을 같다고 취급해야한다. 예를 들어

let f = (x) => x + 3;

이 있다면, f(5)5 + 3 혹은 8을 같은 프로그램으로 취급해야한다. 이 비교 방법의 큰 문제는 함수가 종료하는지 알지 못한다는 것이다. 두 프로그램 ab를 비교하는데, a가 종료하지 않는 함수 l을 호출한다면, 이 알고리즘은 "같음"이나 "같지 않음"이라는 결과를 낼 수조차 없다. 즉, 올바른 판정법이 될 수 없다.

더 심각한 문제는 아직 값을 모르는 변수가 있는 "열린 프로그램"("Open Program")에 대해서도 이런 계산을 고려해야한다는 것이다. 다음의 JavaScript 예시를 보자.

let g = (x) => f(x) + 3;
let h = (x) => (x + 3) + 3;

gh는 같은 프로그램일까? 우리가 gh가 같은 프로그램이기를 원한다면 f(x)x + 3을 같은 프로그램으로 보아야한다. 대부분의 프로그램은 함수 안에서 쓰여지기 때문에 프로그램의 비교는 거의 항상 gh의 몸체와 같은 열린 프로그램들의 비교이다. 따라서 gh를 다른 프로그램으로 본다면 계산을 실행하여 두 프로그램을 비교하는 의미가 퇴색되고 만다. 그렇기 때문에 우리는 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 + 35로 계산하면 되잖아?"

그렇다. 이런 의존 형에 β\beta 동등성을 적용하면 우리가 원하는 형을 바로 얻어낼 수 있다. Vector (2 + 3) aVector 5 a같은 형이기 때문이다. 더욱이, 의존 형의 경우 종료하지 않는 부속 프로그램이 잘못된 형을 줄 수 있기 때문에 많은 경우 종료하지 않는 부속 프로그램을 어차피 포함하지 않는다. 다시 말해, 앞서 말한 제약 조건 즉 모든 부속 프로그램이 종료해야만 한다는 제약조건은 의존 형의 경우 상대적으로 훨씬 덜 심각한 제약조건이 되는 것이다.

이런 의존 형에 있어서의 β\beta 동등성 검사를 "변환 검사"("Conversion Check")라고 하며, 두 형이 β\beta 동등일 경우 이 두 형이 서로 "변환 가능하다"("Convertible")라고 한다. 이 변환 검사는 의존 형이론 구현에 있어서 가장 핵심인 기능 중 하나이며, 가장 잦은 버그를 부르는 기능 중 하나이기도 하다.

마치며

이 글에서는 같음과 같지 않음의 판정 문제에 대해 간략히 설명하고 프로그램의 같음을 판정하는 법에 대해서 단순화하여 다루어보았다. 구체적으로는 문법 기반의 비교와 β\beta 동등성을 통한 비교로 프로그램의 같음을 판정하는 법을 알아보았고, 이 중 β\beta 동등성이 적용되는 가장 중요한 예시인 의존 형이론을 β\beta 동등성을 중점으로 짤막하게 설명하였다. 마지막 문단에서 언급했듯 의존 형이론의 구현에 있어서 β\beta 동등성을 올바르게 구현하는 것은 가장 중요한 작업 중 하나이기에, 최근 연구들은 β\beta 동등성의 구현 자체를 의존 형이론 안에서 함으로서 검증된 β\beta 동등성의 구현을 하기 시작하고 있다. 이 글이 같음과 같지 않음과 판정 문제 그리고 β\beta 동등성에 있어 유용한 설명을 내놓았기를 바라며 이만 줄이도록 하겠다.


  1. 두 함수가 같다라고 보는 방법에 따라 다르나, 두 함수가 항상 같은 값을 가진다면 같다고 하자. 이때 함수의 판정 문제는 정지 문제(Halting Problem)와 동일하다. 임의의 튜링 기계(Turing Machine) ff가 입력 nn을 받았을 때 종료하면 g(n)=1g(n) = 1, 아니면 g(n)=0g(n) = 0이라고 하면 이 함수 gg와 상수 함수 c(n)=1c(n) = 1가 같은 함수임을 보이는 것은 ff가 항상 종료한다는 것을 보이는 것과 동등하다. ↩︎

Read more →
4
2
2
0
0

간혹 "이모지"가 아니라 "에모지"라고 쓰는 이유에 대한 질문을 받습니다. 여기다 써 두면 앞으로 링크만 던지면 되겠지?

요약: 에모지라서 에모지라고 씁니다.

"이모지"라는 표기는 아마도 "emoji"가 "emotion"이나 "emoticon"과 관련이 있다고 생각해서 나오는 것으로 보이는데요. "emoji"와 "emoticon"은 가짜동족어(false cognate)입니다. "emoji"는 일본어 絵文字(에모지)를 영어에서 그대로 받아들여 쓰고 있는 것입니다. 심지어 구성원리도 에모+지가 아니고 에+모지(絵+文字)입니다. "emotion"과 유사해 보이는 것은 순전히 우연일 뿐, 계통적으로 전혀 아무 상관이 없습니다. "이모티콘"과 "이미지"의 합성어가 아닙니다. (그랬으면 "-ji"가 아니라 "-ge"였겠죠.)

그리고 그렇기 때문에 에모지를 에모지로 표기할 실익이 생깁니다. :), ¯\_(ツ)_/¯, ^_^ 등은 이모티콘입니다. 반면 😂는 명확히 에모지입니다.

프로그래머에게 이건 정말 중요한 구분입니다. "이모티콘을 잘 표현하는 시스템"과 "에모지를 잘 표현하는 시스템"은 전혀 다른 과제이기 때문입니다. 에모지는 "그림 문자"라는 원래 뜻 그대로, 어떤 문자 집합(예를 들어 유니코드)에서 그림 문자가 "따로 있는" 것입니다. 내부 표현이야 어떻든, 적어도 최종 렌더링에서는 별도의 글리프가 할당되는 것이 에모지입니다. "무엇이 에모지이고 무엇이 에모지가 아닌가"는 상대적으로 명확합니다(문자 집합에 규정되어 있으니까).

반면 이모티콘은 "무엇이 이모티콘인가?"부터 불명확합니다. 우선 대부분의 이모티콘은 이모티콘이 아닌 문자를 조합하여 이모티콘이 만들어지는 형식입니다. 예를 들어 쌍점(:)이나 닫는 괄호())는 그 자체로는 이모티콘이 아니지만 합쳐 놓으면 :) 이모티콘이 됩니다. 하지만 조합에 새로운 의미를 부여했다고 해서 다 이모티콘이라고 부르지도 않습니다. -_- 같은 것은 대다수가 이모티콘으로 인정하지만, -> 같은 것은 이모티콘이라고 부르지 않는 경향이 있습니다.

- 문자와 > 문자에는 화살표라는 의미가 없기 때문에, -> 조합과 화살표의 시각적 유사성에 기대어 화살표라는 새로운 의미로 "오용"한 것은 이모티콘의 구성 원리에 해당합니다. 하지만 화살표는 인간의 특정한 정서(emotion)에 대응하지 않으므로 이모티콘이라고는 잘 부르지 않습니다. 그렇다고 얼굴 표정을 나타내야만 이모티콘인가 하면 그렇지도 않습니다. orz 같은 것은 이모티콘으로 간주하는 경향이 있어 보입니다. 오징어를 나타내는 <:=는 이모티콘인가? 이모티콘이 맞다면, 왜 ->는 이모티콘이 아니고 <:=는 이모티콘인가? 알 수 없습니다. ㅋㅋㅠㅠ는 둘 다 정서를 나타내는데, ㅠㅠ만이 아이콘적 성질을 가지므로 이모티콘이고 는 이모티콘이 아닌가? 알 수 없습니다. 만약 만 이모티콘이 아니라고 한다면, ㅋ큐ㅠ 에서 는 이모티콘인가 아닌가?? 알 수 없습니다. 이 알 수 없음은 이모티콘의 생래적 성질입니다. 어쩔 수 없죠.

0
0
0

이 글에선 없는, bash보다 Ruby가 좋은 이유가 하니 더 떠오른다.

셸 스크립트를 작성하다보면 필연적으로 awk나 sed 같은 걸 쓰게 되는데, 이게 macOS/BSD와 Linux에서 서로 다른 버전(BSD awk / GNU awk)이 들어있고 지원하는 기능도 미묘하게 다르다보니 작성한 스크립트가 다른 OS에서 작동하지 않을 수 있는데, Ruby나 Perl로 짜면 이런 걱정을 하지 않아도 된다.
hackers.pub/@kodingwarrior/202

CLI 도구를 조합하여 나만의 요술봉 만들기 (feat. Ruby)

<본론으로 들어가기에 앞서, 이 글에서는 Bash 스크립트에다가 Ruby를 섞어서 사용하는 트릭을 서술할 예정인데, 다른 스크립팅 언어로도 해낼 수 있음을 강조해둔다.쉘스크립트로 어떻게 워크플로우를 개선할 수 있을까?어떻게 하면 CLI 도구를 잘 사용할 수 있을까?<이런 고민들, 누군가는 했을 것이다. 이 글을 읽는 여러분은 한번씩은 거치고도 남았을 것이다. 이 글에서는 간단한 커맨드의 조합만으로도 여러분의 삶을 바꿀지도 모르는 비법을 소개하고자 한다. Bash 스크립트를 잘 짜는 방법을 안다면 분명 도움이 되는 구석이 많다. OpenBSD, 리눅스 등을 기반한 어지간한 OS에서는 Bash 스크립트를 지원하기도 하니까 말이다. 하지만, Bash 스크립트 단독으로는 가독성이 떨어지기도 하고, 일회성으로 짠다면 더더욱 직관적으로 와닿는 코드를 짜기도 어렵다. 하지만, Ruby나 Perl 등의 스크립트와 함께라면 그나마 좀 더 가독성이 있는 스크립트를 짤 수 있게 된다.그렇다면 왜 Bash 대신 Ruby인가? Bash를 사용해서 스크립트 짜는 것이 원론적인 접근이고, 많은 곳에서 스크립트를 짤 때 권장하고 있다. 가능하면 외부 소프트웨어와의 의존성을 줄여야 하고, 어디서든 돌아가는 스크립트를 짜야하기 때문이다. 하지만, 내 개발환경에서만 돌리는 일회성의 스크립트라면? Ruby 정도는 섞어서 써도 괜찮다. 사실상 답정너이긴 하지만, 왜 Ruby로 스크립트를 짜는 것이 도움이 되는가? 왜, 꼭 Ruby를 써야하는가? 이유를 나열하자면 아래와 같다.자료형을 다루는 데 있어서 타입 구분이 확실하다내가 다루는 데이터가 어떤 자료형인지 긴가민가한 Bash 스크립트에 비해, Integer/Float/String/Hash/Array 등 명시적으로 구분되는 자료형으로 확실하게 구분되는 점에 감사할 수 있다.여러분이 macOS를 사용하고 있다면, homebrew가 깔려있다면, 사실상 Ruby는 기본으로 딸려오는 옵션이라고 보면 된다.(macOS 유저 한정으로) 빌드 스크립트를 짜는데 요긴하게 도움이 될 수 있다.XCode에서 빌드할때 CocoaPod를 사용하는데, 내부적으로 Ruby 스크립트로 구성이 되어 있다. 또한 Fastlane에서 빌드 스크립트를 작성할때 Ruby를 사용한다. 유사한 작업을 할 때 지식이 전이될 수 있다.JSON/CSV/YAML을 다루는 라이브러리가 표준라이브러리로서 내장이 되어 있다. 이를 어떻게 다룰 수 있는지는 후술하겠다.<Ruby로 One-liner 스크립트 작성하기 보통은 Ruby 코드를 작성할때 irb 같은 대화형 인터페이스를 사용하는 것이 일반적이지만, Bash 스크립트와 섞어서 사용할때는 one-liner 스크립트를 작성하는 것으로 시작한다. 여기서 one-liner 스크립트란 한줄짜리로 실행하는 스크립트라고 이해하면 된다. ruby one-liner 스크립트로 작성할때는 다음과 같이 시작한다.$ ruby -e "<expression>"<여기서 -e 옵션은 one-liner 스크립트의 필수요소인데, 파라미터로 넘겨준 한줄짜리 Ruby 코드를 evaluation해주는 역할을 한다. 여러분이 파이프 혹은 리다이렉션에 대한 개념을 이해하고 있다면, 이런 트릭도 사용할 수 있다.$ echo "5" | ruby -e "gets.to_i.times |t| \{ puts 'hello world' \}"# =># hello world# hello world# hello world# hello world# hello world<여러분이 표준라이브러리를 사용하고 싶을때는 -r 옵션을 사용할 수도 있다. 이 옵션은 ruby에서 require를 의미하는데, 식을 평가하기전에 require문을 미리 선언하고 들어가는 것이라 이해하면 된다. 예를 들면, 이런 것도 가능하다.$ echo "9" | ruby -rmath -e "puts Math.sqrt(gets.to_i)"# => 3.0<위의 스크립트는 아래와 동일하다.$ echo "9" | ruby -e "require 'math'; puts Math.sqrt(gets.to_i)"# => 3.0<이런 원리를 이용하면, JSON/XML/CSV/YAML 등의 포맷으로 출력되는 데이터를 어렵지 않게 처리할 수 있다.다른 CLI 도구와 조합해서 사용해보기 <이 글에서는 여러분이 표준 입출력, 리다이렉션, 그리고 유닉스 기본 명령어(e.g. echo/tail/tead/more/grep 등)는 이미 숙지를 하고 있으리라 생각한다. 이에 대해서 다루자면, 전하고자 하는 의도에 비해 글이 엄청 길어질 수 있어서 의도적으로 생략했다. 혹시나 당장은 모르더라도 상관없다. 그렇게 어렵지 않으니 실습을 한번쯤은 해보는 것을 권장한다.<위에서 예시를 든 것 가지고는 어떻게 하면 나한테 유용한 도구를 만들 수 있는지 파악하기 어려울 수 있다. 그렇다면, 실제로 우리가 사용하고 있는 CLI 도구를 같이 결합해보는건 어떨까? 친숙한 사례를 예시로 들자면 aws-cli, git, gh, jq, curl 등의 커맨드라인 도구가 있을 수 있겠다. 각각은 단일 작업에 특화되어 있어 그 자체로도 훌륭하지만, Ruby 스크립트와 결합했을 때 더욱 강력한 도구로 탈바꿈할 수 있다. 아래에서는 몇 가지 활용 사례와 그로 인해서 어떻게 시너지 효과를 일으킬 수 있는지 살펴보자. 먼저, 간단한 예시를 살펴보자.JSON 데이터 처리하기 JSON 포맷은 어떻게 보면 굉장히 범용적으로 사용되는 포맷이다. API 요청 날릴 때 쓰이는 것은 물론이고, 설정 파일에 쓰이기도 하고, 다른 인터페이스 간 데이터를 교환할 때도 많이 쓰이기도 한다. Ruby에서는 JSON 라이브러리를 표준 라이브러리로 포함하고 있기 때문에, 이것을 굉장히 유용하게 활용할 수 있다.$ curl -s https://jsonplaceholder.typicode.com/posts/1 | ruby -rjson -e 'data = JSON.parse(STDIN.read); puts "Title: #{data["title"]}"'<동작하는 방식은 간단하다.curl로 요청을 날려서 JSON 응답을 반환받는다.JSON 데이터를 파싱하고, 그 중에서 title 필드를 추출한다출력한다.<외부 API에 요청을 날리고 거기서 받은 JSON 응답을 처리하고자 할 때 굉장히 편리하게 처리할 수 있다. 단순히 뽑아내기만 할 때는 jq를 사용하는 것도 방법이긴 하지만, Ruby 스크립트 안에서는 더욱 다양하게 활용할 수 있다. 또한, GitHub CLI/Flutter/AWS CLI 등등이 --format json 같은 옵션을 지원하는데, 한번 섞어서 사용해보는 것을 권장한다.YAML 데이터 처리하기 YAML 포맷은 일반적으로는 설정 파일에 널리 사용된다. Ruby는 기본적으로 yaml 라이브러리를 포함하고 있으므로 YAML 파일을 읽고, 필요한 정보만 출력하는 작업을 쉽게 수행할 수 있다. 예를 들어, config.yaml 파일에서 특정 설정 값을 추출하는 스크립트는 다음과 같다.$ cat config.yaml | ruby -ryaml -e 'config = YAML.load(STDIN.read); puts "Server port: #{config["server"]["port"]}"'<이것도 역시 동작방식은 간단하다.cat 명령어로 config.yaml 파일의 내용을 읽어오고,Ruby의 YAML.load를 사용해 파싱한 후,서버 설정에서 포트 번호를 출력한다.<이와 같이 YAML 파일을 손쉽게 읽어서 원하는 부분만 추출하거나, 구조화된 데이터를 다른 CLI 도구와 연계하여 활용할 수 있다.이도 역시 --format yaml 같은 옵션을 지원하는 kubectl 같은 CLI 도구와 함께 유용하게 사용될 수 있다.정형화되어 있지 않은 복잡한 텍스트 데이터를 처리하기 모든 데이터가 JSON/CSV/YAML 포맷처럼 정형화되어 있으리라는 보장이 없다. 많은 경우 로그 파일, 시스템 메시지, 사용자 입력 등은 형식이 일정하지 않은 경우가 많다. 이런 경우에도 Ruby의 강력한 정규표현식 기능이나 텍스트 처리 능력을 활용하면 데이터를 원하는 형태로 추출하거나 가공할 수 있다. 대부분의 경우에는 높은 확률로 한줄한줄 읽어서 처리하면 되는 경우가 많다. 이번엔, 간단하면서 친숙한 git log를 예시로 살펴보자. 이번에는 글의 주제(one-liner)에서 다소 벗어났지만, 이런 식의 활용도 가능하다는걸 밝히고 싶다. 다음에서 설명하는 스크립트는 내가 굉장히 애용하는 스크립트 중 하나이다. Git 로그 중에서 원하는 커밋을 선택하고, 해당 커밋의 변경사항을 보여준 후, 조회한 내역을 체크리스트 형태로 출력한다.git_logs = `git log --oneline #{file} | gum choose --limit 100`git_logs.each_line do |line| commit_hash, *_ = line.split system("git show #{commit_hash}") puts("=====") puts("Press ENTER key to CONTINUE") puts("=====") getsendchecklist = []git_logs.each_line do |line| checklist << "- [ ] #{line}"endputs checklist.join<이번엔 조금 복잡할 수 있다. 그래도 인내심을 가지고 보면 그렇게 어렵진 않다.<Git 로그 추출:git log --oneline #{file} 명령을 실행하여 한 줄에 하나의 커밋 정보를 출력한다.gum choose --limit 100을 사용해, 출력된 로그 중 조회하고 싶은 라인을 인터랙티브하게 선택할 수 있다.선택된 결과는 git_logs 변수에 저장된다.<각 커밋별 상세 조회:git_logs.each_line을 통해 선택된 로그를 한 줄씩 순회한다.각 줄은 <commit hash> <commit message> 형식을 갖고 있으므로, split 메서드를 사용해 첫 번째 토큰(커밋 해시)만 추출한다.추출한 커밋 해시를 이용해 git show #{commit_hash} 명령을 실행, 해당 커밋의 변경 내용을 출력한다.각 커밋 조회 후, 사용자에게 "계속 진행하려면 ENTER 키를 누르라"는 메시지를 띄워 한 단계씩 진행할 수 있도록 한다.<체크리스트 생성:다시 한 번 git_logs의 각 라인을 순회하며, 각 커밋 로그 앞에 체크리스트 형식(- [ ])을 붙여 리스트 항목을 만든다.모든 항목을 조합해 최종 체크리스트를 출력한다.<마치며 우리는 CLI 도구들과 Ruby 스크립트를 비롯한 다양한 스크립팅 언어를 활용해, 간단한 커맨드 조합만으로도 나만의 비밀무기를 만들 수 있는 방법들을 살펴보았다. 이러한 접근법을 통해 각 도구가 가진 단일 기능의 강점을 그대로 유지하면서, 이를 결합해 더욱 강력하고 유연한 자동화 워크플로우를 구성할 수 있음을 확인했다. 작은 한 줄의 스크립트가 복잡한 데이터 처리, 로그 분석, 버전 관리 작업을 손쉽게 해결해줄 뿐만 아니라, 실제 업무 현장에서 생산성을 극대화할 수 있는 발판이 된다. 여러분도 이번 기회를 계기로 다양한 CLI 도구와 스크립트를 조합하여, 나만의 맞춤형 자동화 도구를 만들어 보는 것은 어떨까? 이 글을 작성하기까지 적지 않은 영향을 주셨던 @ssiumha님께 감사를 표한다.<그 외에도, Perl도 익혀두면 도움이 될 수 있다. Perl은 Git(libgit)이 설치되어 있다면 원플러스원으로 같이 설치되기 때문이다. 요즘은 Perl One-Liners Guide 같은 훌륭한 교재도 있고, LLM도 perl로 one liner 스크립트를 짜달라고 물어보면 뚝딱뚝딱하고 잘 짜주는 편이다.

hackers.pub · Hackers' Pub

Link author: Jaeyeol Lee@kodingwarrior@hackers.pub

0

주말에 했던 〈국한문혼용체에서 Hollo까지〉의 발표에서 대강:

  1. 연합우주에서도 국한문혼용체로 글을 쓰는데 사람들이 읽기 쉽게 한글 독음을 <ruby> 태그로 달고 싶다!
  2. Mastodon에 패치를 할 수는 있지만 업스트림에 받아들여질 리가 없으니 패치된 Mastodon 서버를 직접 운영할 수 밖에 없다.
  3. 혼자 쓰자고 Mastodon 서버를 운영하려니 비용이 크다. 가벼운 ActivityPub 서버 구현을 만들어야겠다.
  4. ActivityPub 서버 구현을 바닥부터 하자니 할 일이 너무 많다. ActivityPub 프레임워크를 만들어야겠다. → Fedify
  5. Fedify를 만들었으니 일인 사용자용 ActivityPub 서버를 구현하자. → Hollo
  6. Hollo를 만들었으니 Seonbi를 연동하자.
  7. Seonbi를 연동하여 한자어에 한글 독음이 <ruby>로 달게 했으나 Mastodon과 Misskey에서 <ruby> 태그를 지원 안 한다.
  8. Mastodon 쪽에 이슈를 만들고 연합우주에서 홍보하여 결국 패치됨. Mastodon에서도 <ruby> 지원!
  9. Misskey 쪽에 패치를 보내서 결국 Misskey에서도 <ruby> 지원!
  10. 행복한 국한문혼용 생활.

이상과 같은 이야기를 했더니, “야크 셰이빙이 엄청나다”라는 의견을 들었다.



RE: https://hackers.pub/@hongminhee/019603e0-33ef-700f-90f4-153c8c995135

0
0
0

회사에서 쓴 코드를 공개 저장소에 올릴 수는 없어서 hackage-server를 직접 돌려보기로 했다. 빌드할 때 의존성 맞추기가 어려울 것 같아서 속는 셈 치고 Nix를 써봤는데⋯ 한 번에 서버가 에러 없이 실행됐다. 이제 사내에서 하스켈 패키지 관리를 할 수 있다!(그런데 아무도 안 씀⋯)

0

Powershell은. Net기반이라. Net 라이브러리도 가져다 쓸 수 있고 좀 더 이상하게까지 가면 C#코드를 임베딩해서 쓸 수도 있다. 어쨌든 셸 이지만 모든 것이 문자열이 아닌 납득할만한 타입이 있는 셸이다. 그리고 크로스 플랫폼도 지원한다. 하지만 몇몇 명령어는 어째 특정 경우의 동작이 괴상하고, 나중에 나온 크로스 플랫폼 버전에서만 납득 할만한 동작을 하게 하는 플래그가 제공 되기도 한다. 예를 들어 ConvertTo-Json은 json 문자열로 직렬화 해주는 명령인데, https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/convertto-json?view=powershell-7.5#-asarray 옵션이 없으면 요소가 한개인 배열의 경우 그 요소만 꺼내서 직렬화 해버린다.

0

패키지 저장소에 내가 만든 패키지를 업로드해보니까 다음과 같이 코딩말고도 할 게 꽤 많다.

  • 저장소 계정 만들고 권한 승인 받기[1]
  • 라이브러리 코드에 주석 적기
  • 라이선스 정하기
  • 소스 코드 저장소 정하기
  • 커밋 메시지 적기
  • 체인지로그 적기
  • 테스트 코드 적기
  • 버전 관리하기[^4]

LLM 덕분에 영어로 적어야 하는 문서 대부분은 어렵지 않게 할 수 있었다. LLM 때문에 바보 된다는 말도 많지만 영어 때문에 주저 했던 작업을 쉽게 시작할 수 있었다는 점에서 확실히 진입 장벽은 낮아졌다.

앞으로 더 해볼 일은 특정 배포판에 패키지를 배포하는 일이다. 해키지에서 지원하는 배포판은 다음과 같다.

  • Arch
  • Debian
  • Fedora
  • FreeBSD
  • LTS
  • LTSHaskell
  • NixOS
  • Stackage
  • openSUSE

도전 과제로 위에 적은 모든 배포판에 내가 만든 패키지 배포해보는 것도 재밌겠다. LTS, LTSHaskell은 뭔가 했는데, 스태키지(Stackage)에 패키지 업로드하는 방법을 안내한 문서를 보니까 스태키지의 저장소 종류인 것 같다.

한편 해커즈 퍼브의 홍민희 님도 헤비(?) 하스켈 패키지 업로더이신데 예를 들어 seonbi도 사실 하스켈 패키지이다.

해키지 업로더 계정이 필요하신 분은 나와 홍민희 님을 멘션하면 이미 두 명의 승인은 따 놓은 당상이다.


  1. 해키지에서는 가입 후 기존 해키지 업로더 2명의 승인이 필요하다. ↩︎

0
0
0

연합우주 계정의 이전에 관한 규격인 FEP-7628를 구현하고 있습니다. 아직 Hackers' Pub 계정을 다른 계정으로 이전하거나, 다른 계정을 Hackers' Pub 계정으로 이전하는 기능은 구현되지 않았지만, 그 밑작업으로서 FEP-7628을 이미 구현한 Mastodon, Misskey, Pleroma 등의 계정끼리 서로 이전한 것을 반영하는 기능은 구현되었습니다. 예를 들어, 제 옛날 계정 중 하나인 @hongminhee洪 民憙 (Hong Minhee) 계정을 보면 첨부한 캡처 화면처럼 계정의 이전 안내가 표시되게 됩니다.

Hackers' Pub에서 더이상 사용되지 않는 계정의 프로필 화면. 새로운 계정으로 이전되었다는 메시지가 상단에 표시되고, 옛 계정의 프로필 사진은 흑백으로 보인다.
0
0
0

https://newsletter.posthog.com/p/50-things-weve-learned-about-building

PostHog가 성공적인 제품을 만들면서 깨달은 50가지 교훈 (뉴스레터에서 지금까지 발행해놓은 것들 오마카세처럼 모아놓음)

그 중에서 마음에 드는 것들을 몇개 뽑아보자면...

  1. 신뢰는 투명성에서 온다. Build in Public 같은 것이 도움이 될 수 있음
  2. 시장에 내놓지 않으면 검증 조차 할 수 없음. 일단 시장에 내놓고 반응을 살펴볼 것
  3. 개밥먹기를 통해서 고객에게 전달되기 전에 문제점을 빠르게 인식하고 개선할 수 있는 흐름을 만들 것
0

보통은 Ruby 코드를 작성할때 irb 같은 대화형 인터페이스를 사용하는 것이 일반적이지만, Bash 스크립트와 섞어서 사용할때 one-liner 스크립트를 작성하면 더욱 빛을 발휘합니다. 여기서 one-liner 스크립트란 한줄짜리로 실행하는 스크립트라고 이해하면 됩니다. ruby one-liner 스크립트로 작성할때는 다음과 같이 시작합니다.

$ ruby -e "<expression>"

여기서 -e 옵션은 one-liner 스크립트의 필수요소인데, 파라미터로 넘겨준 한줄짜리 Ruby 코드를 evaluation해주는 역할을 합니다. 여러분이 **파이프 연산자**에 대한 개념을 이해하고 있다면, 이런 트릭도 사용할 수 있습니다.

$ echo "5" | ruby -e "gets.to_i.times |t| \{ puts 'hello world' \}"
# =>
# hello world
# hello world
# hello world
# hello world
# hello world

표준라이브러리를 사용하고 싶을때는 -r 옵션을 사용할 수도 있습니다. 이 옵션은 ruby에서 require를 의미하는데, 식을 평가하기전에 require문을 미리 선언하고 들어가는 것이라 이해하면 됩니다.

예를 들면, 이런 것도 가능합니다 .

$ echo "9" | ruby -rmath -e "puts Math.sqrt(gets.to_i)"
# => 3.0

위의 스크립트는 아래와 동일합니다.

$ echo "9" | ruby -e "require 'math'; puts Math.sqrt(gets.to_i)"
# => 3.0

이런 원리를 이용하면, JSON/XML/CSV/YAML 등의 포맷으로 출력되는 데이터를 어렵지 않게 처리할 수 있습니다. one-liner 스크립트를 작성하는 방법에 대해 자세히 알아가고 싶다면, https://learnbyexample.github.io/learn_ruby_oneliners/one-liner-introduction.html 여기를 참고해주시면 좋을 것 같습니다.

이에 관해서, 25분-30분 정도 분량의 글을 정리해서 올릴 것 같은데 커밍쑨.....

0

프론트엔드 개발자분들 위주로 페디버스로 영입을 많이 해보고 싶다. 포트폴리오로 개발할만한 것이 지천에 널려있는데...!!

  • 인스타그램 클론코딩 : PixelFed 클라이언트 개발에 적용하기
  • 트위터 클론코딩 : 마스토돈 클라이언트 개발할때 적용하기
  • 나만의 블로그 만들기 : 이건.... 해커스펍이 그 역할을 하겠지...?
0