lionhairdino

@lionhairdino@hackers.pub · 53 following · 60 followers

지금까지 다루어 봤던 언어는 아래와 같습니다. MSX Basic Z80 Assembly Pascal GW-Basic C Macromedia Director Visual Basic PHP Flash Actionscript C++ Javascript

그리고 지금은, 하스켈을 비즈니스에 쓰려고 몇 년간 노력하고 있습니다. 지금 상태는, 하스켈 자체를 연구하는 게 아니라, 하스켈 (혹은 함수형 언어) 이해가 어려운 이유를 연구하는 아마추어 연구가쯤 되어버렸습니다. 하스켈 주제로 블로그를 운영 중이지만, 아직은 하스켈 프로그래머라고 자신 있게 말하진 못하고 있습니다. 가끔 이해에 도움이 될만한 측면이 보이면, 가볍게 아이디어를 여러 SNS에 올려보곤 하는데, 그다지 프로그래머에게 쓸모 있는 내용이 포함되진 않는 것 같습니다.

Blog
lionhairdino.github.io

Hackers' Public @ Seoul 1회차 모임 (1차 모집)

서울에서 열리는 Hackers' Pub 오프라인 밋업, "Hackers' Public @ Seoul"이 2025월 9월 14일(일) 처음으로 개최됩니다. 처음 열리는 밋업인 만큼, 참여하는 많은 분들이 재밌게 느낄 수 있는 소재 위주로 연사자 분들을 섭외했습니다.

  • 일시 : 9월 14일 (일) 오후 3시 ~ 오후 6시
  • 장소 : 서울특별시 성동구 상원길 26, 튜링의사과
  • 주제
    • Code As A Canvas : 코드에서 예술작품이 되기까지 (@jakeseo)
    • 폰트는 어떻게 만들어지는가 - NeoDGM 사례로 살펴보는 개발 후일담 (@dalgona)

강연이 끝나고 난 뒤에 자유롭게 네트워킹하는 시간을 가질 예정입니다. 각자 얘기하고 싶은 주제를 들고 오시면 좋습니다.

참여 신청

오프라인 밋업은 여기서 참여신청이 가능합니다. https://event-us.kr/hackerspubseoul/event/110961

  • 모집 기간
    • 1차 모집 : 8월 27일 ~ 9월 1일 (Hackers' Pub에서만 모집)
    • 2차 모집 : 9월 3일 ~ 9월 7일 (Hackers' Pub 외부에서도 공개적으로 모집)

주의사항

  • 본 행사는 Hackers' Pub에서 진행하는 오프라인 행사이며, Hackers' Pub 계정을 가지지 않은 분이 신청하셨을 경우 환불처리될 수 있습니다.
  • Hackers' Pub 외부에서 유입하시는 경우, 각 모집기간이 끝나고 24시간 안에는 Hackers' Pub에 가입이 되어 있으셔야 참여자로 확정됩니다.
13
2
2
3

lionhairdino shared the below article:

파서 콤비네이터: 하스켈 초보자를 위한 파싱

박준규 @curry@hackers.pub

이 글은 하스켈 초보자를 위한 파서 컴비네이터에 대한 입문 튜토리얼입니다. 파싱은 프로그래밍에서 흔히 발생하는 작업이지만, 정규 표현식이나 문자열 조작만으로는 복잡한 형식을 다루기 어렵습니다. 저자는 `Text.ParserCombinators.ReadP` 라이브러리를 사용하여 파서 컴비네이터를 소개하고, 이를 통해 더 읽기 쉽고 유지보수가 용이한 파서를 작성할 수 있음을 보여줍니다. METAR 보고서 파싱 예제를 통해 `satisfy`, `many1`, `<|>`, `option` 등의 기본적인 파서 콤비네이터 함수를 설명하고, 펑터와 모나드의 개념을 활용하여 파서를 구성하는 방법을 안내합니다. 또한, 파싱된 데이터의 유효성을 검사하고, 결과를 더 의미 있는 데이터 타입으로 변환하는 방법을 제시합니다. 이 튜토리얼을 통해 독자는 파서 컴비네이터의 기본 원리를 이해하고, 실제 데이터 파싱 작업에 적용할 수 있는 능력을 얻게 됩니다. 마지막으로, 저자는 독자들에게 배운 내용을 바탕으로 전체 METAR 보고서를 파싱하는 라이브러리를 만들어 Hackage에 제출해 볼 것을 권장하며, 파서가 없는 데이터를 만났을 때 `ReadP`를 자신 있게 사용할 수 있기를 바랍니다.

Read more →
11
14
2
1

lionhairdino shared the below article:

내가 지금 바이브코딩을 하고 싶어도 못하는 상황이라 답답한데

bgl gwyng @bgl@hackers.pub

이 글은 앱 개발 과정에서 프론트엔드, 백엔드, 그리고 라이브러리 개발에 AI를 활용하는 경험과 고민을 담고 있습니다. 특히, 간단한 CRUD 작업은 이미 자동화가 가능하지만, 복잡한 mutation 개발에는 테스트 환경 구축이 선행되어야 AI의 도움을 효과적으로 받을 수 있음을 강조합니다. 또한, 라이브러리 개발 자동화의 잠재적 위험성을 지적하며, 이는 코드의 '축적'이라는 특성상 현재 AI 수준으로는 어렵다고 분석합니다. 궁극적으로, 코드 에이전트 사업의 미래와 협업을 통한 코드 축적의 중요성을 역설하며, Gen AI를 넘어선 Zen AI의 필요성을 제기합니다. 이 글은 AI 개발 도구의 현황과 한계를 현실적으로 진단하고, 미래 개발 환경에 대한 깊이 있는 통찰을 제공합니다.

Read more →
7

lionhairdino shared the below article:

How to pass the invisible

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub

This post explores the enduring challenge in software programming of how to pass invisible contextual information, such as loggers or request contexts, through applications without cumbersome explicit parameter passing. It examines various approaches throughout history, including dynamic scoping, aspect-oriented programming (AOP), context variables, monads, and effect systems. Each method offers a unique solution, from the simplicity of dynamic scoping in early Lisp to the modularity of AOP and the type-safe encoding of effects in modern functional programming. The post highlights the trade-offs of each approach, such as the unpredictability of dynamic scoping or the complexity of monad transformers. It also touches on how context variables are used in modern asynchronous and parallel programming, as well as in UI frameworks like React. The author concludes by noting that the art of passing the invisible is an eternal theme in software programming, and this post provides valuable insights into the evolution and future directions of this critical aspect of software architecture.

Read more →
11
1
0

같은 얘기 한 적 있는데, 유전자 발현(expression)을 계산(evaluation)하거나 평가(evaluation)하지 않듯이, 표현(expression)을 평가(evaluation)는 해도 계산(evaluation) 또는 (표현의) "값을 구한다(evaluate)"고 하지 않듯이, 식(expression)도 평가(evaluation)하지 않고, '값을 구한다(evaluate)'거나 계산(evaluation)했으면 좋겠다고 생각해 봅니다.

5

안녕하세요, 업으로 프로그래밍을 하고 있는 컴퓨터 학부생 김무훈입니다.
현재 3년차 웹 프론트엔드 개발자로서, 다가오는 7월부터 함께할 정규직 포지션을 적극적으로 찾고 있습니다.

최근 학과 사무실에서 졸업 요건을 확인한 결과, 전공 필수 한 과목전공 선택 2학점(총 5학점)이 남아있음을 확인했습니다.
본래는 다음 2학기까지 수료 후 내년 2월에 졸업할 예정이었으나, 교수진과 상의한 결과 취업 및 재직이 확정된다면 수업 이수 방식을 보다 유연하게 결정할 수 있다는 긍정적인 답변을 받아 적극적으로 조기 취업을 추진하게 되었습니다.

이는 전공 필수 과목의 경우에만 해당이 되는 문제이고, 전공 선택 2학점의 경우 앞으로의 여름 학기 현장 실습 또는 다음 학기에 개설되는 하나의 원격 강의로 대체하여 문제가 없는 상태입니다.

지금까지의 업무 경험과 프로젝트는 아래의 포트폴리오에서 확인하실 수 있습니다.
📌 경력기술서 겸 포트폴리오 페이지: https://www.frontend.moe/portfolio/

좋은 인연을 찾을 수 있도록, 많은 관심과 연락 부탁드립니다!

8

뉴럴넷을 설계할수 있는 GUI를 프로토타이핑 해야하는데 좋은 방향이 생각이 안난다. 첨에 착수할땐 자명하다고 생각했는데, 막상 시작하고나니 의외로 참고할 물건도 적고 난감한 상태다.

3

lionhairdino shared the below article:

논리와 메모리 - 논리와 저수준(Low-level) 자료 표현(Data representation) (2 편 중 2 편)

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

이 글은 "논리적"이 되는 두 번째 방법인 논건 대수를 재조명하며, 특히 컴퓨터 공학적 해석에 초점을 맞춥니다. 기존 논건 대수의 한계를 극복하기 위해, 컷 규칙을 적극 활용하는 반(半)공리적 논건 대수(SAX)를 소개합니다. SAX는 추론 규칙의 절반을 공리로 대체하여, 메모리 주소와 접근자를 활용한 저수준 자료 표현과의 커리-하워드 대응을 가능하게 합니다. 글에서는 랜드(∧)와 로어(∨)를 "양의 방법", 임플리케이션(→)을 "음의 방법"으로 구분하고, 각 논리 연산에 대한 메모리 구조와 연산 방식을 상세히 설명합니다. 특히, init 규칙은 메모리 복사, cut 규칙은 메모리 할당과 초기화에 대응됨을 보여줍니다. 이러한 SAX의 컴퓨터 공학적 해석은 함수형 언어의 저수준 컴파일에 응용될 수 있으며, 논리와 컴퓨터 공학의 연결고리를 더욱 강화합니다. 프랭크 페닝 교수의 연구를 바탕으로 한 SAX는 현재도 활발히 연구 중인 체계로, ML 계열 언어 컴파일러 개발에도 기여할 수 있을 것으로 기대됩니다.

Read more →
12

부끄럽지만 typst로 깎은 이력서와 포트폴리오를 공개합니다: https://github.com/gidongkwon/resume

게임 클라이언트에서 웹 프론트엔드로 커리어 전환을 하는 단계에 있습니다.
혹 피드백주실 것이 있다면 언제든지 좋아요...!

직링크는 아래:
이력서 - https://gidongkwon.github.io/resume/resume-gidongkwon.pdf
포트폴리오 - https://gidongkwon.github.io/resume/portfolio-gidongkwon.pdf

4

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

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

4

lionhairdino shared the below article:

함수형 언어의 평가와 선택

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

함수형 언어(Functional Language)의 핵심

함수형 언어가 점점 많은 매체에 노출되고, 더 많은 언어들이 함수형 언어의 특징을 하나 둘 받아들이고 있다. 함수형 언어, 적어도 그 특징이 점점 대세가 되고 있다는 이야기이다. 하지만, 함수형 언어가 대체 무엇이란 말인가? 무엇인지도 모르는 것이 대세가 된다고 할 수는 없지 않은가?

함수형 언어란 아주 단순히 말해서 함수가 표현식[1]인 언어를 말한다. 다른 말로는 함수가 이기 때문에 다른 함수를 호출해서 함수를 얻어내거나 함수의 인자로 함수를 넘길 수 있는 언어를 말한다. 그렇다면 이 단순화된 핵심만을 포함하는 언어로 함수형 언어의 핵심을 이해할 수 있지 않을까? 이게 바로 람다 대수(Lambda Calculus)의 역할이다.[2]

람다 대수는 딱 세 종류의 표현식만을 가지고 있다.

  1. 변수 (xx, yy, …\ldots)
  2. 매개변수 xx에 인자를 받아 한 표현식 MM(함수의 몸체)을 계산하는 함수 (λx→M\lambda x\to M)
  3. 어떤 표현식 LL의 결과 함수를 인자 NN으로 호출 (L NL\ N)

이후의 설명에서는 MMNN, 그리고 LL이라는 이름을 임의의 표현식을 나타내기 위해 사용할 것이다. 람다 대수가 어떤 것들을 표현할 수 있는가? 앞에서 말했듯이 람다 대수는 함수의 인자와 함수 호출의 결과가 모두 함수인 표현식을 포함한다. 예를 들어 λx→(λy→y)\lambda x \to (\lambda y \to y) 는 매개변수 xx에 인자를 받아 함수 λy→y\lambda y \to y를 되돌려주는 함수이고, λx→(x (λy→y))\lambda x \to (x\ (\lambda y \to y))는 매개변수 xx에 함수인 인자를 받아 그 함수를 (λy→y\lambda y \to y를 인자로 사용하여) 호출하는 함수이다.

람다 대수(Lambda Calculus)의 평가(Evaluation)

이제 문제는

그래서 람다 대수의 표현식이 하는 일이 뭔데?

이다. 위의 표현식에 대한 소개는 산수로 말하자면 x+yx + y와 같이 연산자(++)와 연산항(xxyy)로부터 얻어지는 문법만을 설명하고 있고, 3+53 + 5와 같은 구체적인 표현식을 계산해서 88이라는 결과 값을 내놓는 방식을 설명하고 있지 않다. 이런 표현식으로부터 값을 얻어내는 것을 언어의 "평가 절차"("Evaluation Procedure")라고 한다. 람다 대수의 평가 절차를 설명하는 것은 어렵지 않다. 적어도 표면적으로는 말이다.

  • 함수는 이미 값이다.
  • 함수 λx→M\lambda x \to MNN으로 호출하면 MM에 등장하는 모든 xxNN으로 치환(Substitute)하고 결과 표현식의 평가를 계속한다.

이는 겉으로 보기에는 말이 되는 설명처럼 보인다. 하지만 이 설명을 실제로 해석기(Interpreter)로 구현하려고 시도한다면 이 설명이 사실 여러 세부사항을 무시하고 있다는 점을 깨닫게 될 것이다.

  1. 함수 호출 L NL\ N에서 LL이 (아직) λx→M\lambda x \to M 꼴이 아닐 때는 어떻게 해야하지?
  2. 함수 호출 (λx→M) N(\lambda x \to M)\ N에서 NN을 먼저 평가하는 게 낫지 않나? xxMM에 여러번 등장한다면 NN을 여러번 평가해야 할텐데?

첫번째 문제는 비교적 간단히 해결할 수 있다. LL을 먼저 평가해서 λx→M\lambda x \to M 꼴의 결과 값을 얻어낸 뒤에 호출을 실행하면 되기 때문이다. 반면에 두번째 질문은 좀 더 미묘한 문제를 가지고 있다. 함수 호출의 평가에서 발생하는 이 문제에 구체적인 답을 하기 위해서는 값에 의한 호출(Call-By-Value, CBV)와 이름에 의한 호출(Call-By-Name, CBN)이 무엇인지 이해해야 한다.

값에 의한 호출(Call-By-Value)? 이름에 의한 호출(Call-By-Name)?

앞에서 말한 함수 호출에서부터 발생하는 문제는 사실 함수형 언어에서만 발생하는 문제는 아니다. C와 같은 명령형 언어에서도 함수를 호출할 때 인자를 먼저 평가해야하는지를 결정해야하기 때문이다. 즉 이 문제는 함수를 가지고 있고 함수를 호출해야하는 모든 언어들이 가지고 있는 문제이다.

그렇다면 이 일반적인 문제를 어떻게 해결하는가? 대부분의 언어가 취하는 가장 대표적인 방식은 "값에 의한 호출"("Call-By-Value", "CBV")이라고 한다. 이 함수 호출 평가 절차에서는 함수의 몸체에 인자를 치환하기 전에[3] 인자를 먼저 평가한다. 이 방식을 사용하면 인자를 여러번 평가해야하는 상황을 피할 수 있다.

또 다른 방식은 "이름에 의한 호출"("Call-By-Name", "CBN")이라고 한다. 이 방식에서는 함수의 몸체에 인자를 우선 치환한 후 몸체를 평가한다. 몇몇 언어의 매크로(Macro)와 같은 기능이 이 방식을 사용한다. 얼핏 보기에는 CBN은 장점이 없어보인다. 그러나 함수가 인자를 사용하지 않을 경우는 CBN이 장점을 가진다는 것을 볼 수 있다. 극단적으로 평가가 종료되지 않는 표현식(Non-terminating expression)이 있다면[4] CBV는 종료하지 않고 CBN만이 종료하는 경우가 있음을 다음 예시를 통해 살펴보자. 표현식 (λx→(λy→y)) N(\lambda x \to (\lambda y \to y))\ N이 있다고 할 때, NN이 평가가 종료되지 않는 표현식이라고 하자. 이 경우 CBV를 따른다면 종료하지 않는 NN 평가를 먼저 수행하느라 이 표현식의 값을 얻어낼 수 없지만, CBN을 따른다면 λy→y\lambda y \to y라는 값을 손쉽게 얻어낼 수 있다. 바로 이런 상황 때문에

CBN은 CBV보다 일반적으로 더 많은 표현식들을 평가할 수 있다

고 말한다.

모호한 선택을 피하는 방법

두 방식의 장점을 모두 가질 수는 없을까? 다시 말해서, 어떤 상황에서는 이름에 의한 호출을 사용하고, 어떤 상황에서는 값에 의한 호출을 사용할 수 없을까? 이 질문에 답한 수많은 선구자들 가운데 폴 블레인 레비(Paul Blain Levy)가 내놓은 답인 "값 밀기에 의한 호출"("Call-By-Push-Value", "CBPV")은 함수형 언어의 평가를 기계 수준(Machine level)에서 이해하는데에 있어 강력한 도구를 제공한다. CBPV는 우선 "계산"("Computation")과 "값"("Value")을 구분한다.

  • 계산 MM, NN, LL, …\ldots = 함수 λx→M\lambda x \to M 또는 함수 호출 L VL\ V
  • VV, UU, WW, …\ldots = 변수 xx

잠깐, 앞서서 함수형 언어에서 함수는 값이라고 하지 않았던가? 이는 값 밀기에 의한 호출에서 함수와 함수 호출을 종전과 전혀 다르게 이해하기 때문이다. 함수 λx→M\lambda x \to M는 스택(Stack)에서 값을 빼내어(Pop) xx라는 이름을 붙인 후 MM을 평가하는 것이고, 함수 호출 L VL\ V는 스택에 값 VV를 밀어넣고(Push)[5] LL을 평가하는 것이다. 따라서 함수 λx→M\lambda x \to M는 평가의 결과가 아닌 추가적인 평가가 가능한 표현식이 된다. 이 구분을 간결하게 설명하는 것이 다음의 CBPV 표어이다.

값은 "~인 것"이다. 계산은 "~하는 것"이다.

그렇지만 함수형 언어이기 위해서는 함수를 값으로 취급할 수 있어야 한다고 했지 않은가? 그렇다. 이를 위해 CBPV는

계산을 강제한다면(force\mathbf{force}) 계산 MM를 하는 지연된 계산인 값 thunk(M)\mathbf{thunk}(M)

을 추가로 제공한다. 이 둘 (force(V)\mathbf{force}(V)thunk(M)\mathbf{thunk}(M))을 다음과 같이 문법에 추가할 수 있다.

  • 계산 = λx→M\lambda x \to M 또는 L VL\ V 또는 force(V)\mathbf{force}(V)
  • 값 = xx 또는 thunk(M)\mathbf{thunk}(M)

CBPV를 완성하기 위해 필요한 마지막 조각은 계산을 끝내는 법이다. 현재까지 설명한 λx→M\lambda x \to ML VL\ V 그리고 force(V)\mathbf{force}(V) 는 모두 다음 계산을 이어서 하는 표현식이고, 계산을 끝내는 방법을 제공하지는 않는다. 예를 들어 λx→M\lambda x \to M의 평가는 스택에서 값을 빼내고 계산 MM의 평가를 이어한다. 그렇다면 계산의 끝은 무엇인가? 결과 값을 제공하는 것이다. 이를 위해 return(V)\mathbf{return}(V)를 계산에 추가하고, 이 결과 값을 사용할 수 있도록 M to x→NM\ \mathbf{to}\ x \to N (계산 MM을 평가한 결과 값을 xx라고 할 때 계산 NN을 평가하는 계산) 또한 계산에 추가하면 다음의 완성된 CBPV를 얻는다.

  • 계산 = λx→M\lambda x \to M 또는 L VL\ V 또는 force(V)\mathbf{force}(V) 또는 return(V)\mathbf{return}(V) 또는 M to x→NM\ \mathtt{\mathbf{to}}\ x \to N
  • 값 = xx 또는 thunk(M)\mathbf{thunk}(M)

이제 CBPV를 얻었으니 원래의 목표로 돌아가보자. 어떻게 CBV 호출과 CBN 호출을 CBPV로 설명할 수 있을까?

  • CBV 함수 λx→M\lambda x \to M와 호출 L NL\ N이 있다면, 이를 return(thunk(λx→M))\mathbf{return}{(\mathbf{thunk}(\lambda x \to M))}L to x→N to y→force(x) yL\ \mathbf{to}\ x \to N\ \mathbf{to}\ y \to \mathbf{force}(x)\ y로 표현할 수 있다. 즉, CBPV의 관점에서 CBV의 함수는 지연된 원래 계산 λx→M\lambda x \to M을 값으로 되돌려주는 계산으로 이해할 수 있고, 함수 호출 L NL\ N은 함수 부분 LL을 먼저 평가하고 NN을 평가한 뒤 NN의 계산 결과 yy를 스택에 밀어넣고 지연된 계산인 함수 부분 xx의 계산을 강제하는(force(x)\mathbf{force}(x)) 것으로 이해할 수 있다.
  • CBN 함수 λx→M\lambda x \to M와 호출 L NL\ N이 있다면, 이를 λx→M\lambda x \to M(단, 변수 xx의 모든 사용을 force(x)\mathbf{force}(x)로 치환함)과 L thunk(N)L\ \mathbf{thunk}(N)로 표현할 수 있다. 즉, CBPV의 관점에서 함수 호출은 L NL\ N은 지연된 NN을 스택에 밀어넣은 뒤 LL의 계산을 이어가는 것으로 볼 수 있다. 이 지연된 NN은 이후에 스택에서 빼내어져 어떤 이름 xx가 붙은 뒤, 이 변수가 사용될 때에야 비로소 계산된다.

다소 설명이 복잡할 수 있으나, 단순하게 말해서 CBPV는 CBV에 따른 상세한 평가 순서와 CBN 따른 상세한 평가 순서를 세부적으로 설명할 수 있는 충분한 기능을 모두 갖추고 있으며, 이를 통해 CBV 함수 호출과 CBN 함수 호출을 모두 설명할 수 있다는 이야기이다.

기계 수준(Machine level)에서의 Call-By-Push-Value의 장점

앞에서는 CBPV가 CBV와 CBN를 모두 설명할 수 있음을 다뤘다. 그러나 CBPV는 프로그래머(Programmer)가 직접 사용하기에는 과도하게 자세한 세부사항들을 포함하고 있기에, 프로그래머가 직접 CBPV를 써서 CBV와 CBN의 구분을 조율하기에는 적합하지 않다. 그렇다면 어느 수준에서 CBV와 CBN을 혼합해 사용할 때 도움을 줄 수 있을까? 바로 람다 대수를 기계 수준으로 컴파일(Compile)할 때이다. 이때는 CBPV가 가진 자세한 세부사항의 표현력이 굉장히 유용해진다.

예를 들어 람다 대수를 기계 수준으로 변환할 때 흔히 필요한 것 중 하나인 항수 분석(Arity analysis)에 대해 이야기해보자. 항수 분석은 함수가 하나의 인자를 받은 뒤 실행되어야 하는지, 혹은 두 인자를 모두 받아 실행되어야 하는지 등을 확인하여 이후에 그에 걸맞는 최적화된 기계어(Machine language)를 생성할 수 있게 도와주는 분석 작업이다. 평범한 람다 대수에서는 항수 분석의 결과를 직접적으로 표현하기 어렵다. 예를 들어 람다 대수의 λx→(λy→y)\lambda x \to (\lambda y \to y)의 경우 이 함수가 xxyy를 모두 받아 yy를 되돌려주는 함수인지 (항수가 2인 함수인지), 혹은 xx를 받아 λy→y\lambda y \to y라는 함수를 되돌려주는 함수인지 (항수가 1인 함수인지) 구분할 수 없다. 그러나 이를 CBPV로 변환한 λx→(λy→return(y))\lambda x \to (\lambda y \to \mathtt{return}(y))λx→return(thunk(λy→return(y)))\lambda x \to \mathtt{return}(\mathtt{thunk}(\lambda y \to \mathtt{return}(y)))는 각각이 무엇을 뜻하는지 분명히 이해할 수 있다.

  • λx→(λy→return(y))\lambda x \to (\lambda y \to \mathtt{return}(y))는 두 변수 xxyy를 스택에서 빼낸 뒤 yy의 값을 되돌려주는 함수(항수가 2인 함수)이다.
  • λx→return(thunk(λy→return(y)))\lambda x \to \mathtt{return}(\mathtt{thunk}(\lambda y \to \mathtt{return}(y)))는 변수 xx를 스택에서 빼낸 뒤 지연된 계산 λy→return(y)\lambda y \to \mathtt{return}(y)를 돌려주는 함수(항수가 1인 함수)이다.

이런 장점을 바탕으로 CBPV를 더 발전시킨 "언박싱한 값에 의한 호출"("Call-By-Unboxed-Value")을 GHC 컴파일러의 중간 언어(Intermediate language)로 구현하는 것에 대한 논의가 현재 진행되고 있으며 앞으로 더 많은 함수형 컴파일러들이 관련된 중간 언어를 채용하기 시작할 것으로 보인다.

마치며

이 글에서는 함수형 언어의 핵인 람다 대수를 간단히 설명하고 람다 대수를 평가하는 방법에 대해서 다루어보았다. 특히 그 중 값 밀기에 의한 평가(Call-By-Push-Value, CBPV)가 무엇이며 CBPV가 다른 대표적인 두 방법(CBV, CBN)을 어떻게 표현할 수 있는지, 그리고 CBPV의 장점이 무엇인지에 대해서도 다루어 보았다. 이 글에서 미처 다루지 못한 중요한 주제는 CBPV를 기계에 가까운 언어로 번역해보는 것이다. 여기에서는 글이 너무 복잡해지는 것을 피하기 위해 제했으나, CBPV의 장점에서 살펴봤듯 이는 CBPV에 있어 핵심 주제 중 하나이기 때문에 이후에 다른 글을 통해서라도 이 주제를 소개할 기회를 가지고자 한다. 이 글이 CBPV에 대한 친절한 소개글이었기를 바라며 이만 줄이도록 하겠다.


  1. 결과 (Value)을 가지는 언어 표현을 말한다. 예를 들어 1+11 + 122라는 값을 가지는 표현식이지만 (JavaScript의) let x = 3;나 (Python의) def f(): ...은 그 자체로는 값이 없기 때문에 표현식이 아니다. ↩︎

  2. 다만 실제 역사에서는 람다 대수의 이해와 발견이 함수형 언어의 개발보다 먼저 이루어졌다. 이런 역사적 관점에서는 (이미 많은 수학자들이 이해하고 있던) 람다 대수에 여러 기능을 추가한 것이 바로 함수형 언어라고 볼 수 있다. ↩︎

  3. 프로그래밍 언어(Programming Language)는 실제로는 치환을 사용하지 않고 환경(Environment)을 사용하는 경우가 더 많지만 설명의 편의를 위해 다른 언어들 또한 환경 대신 치환에 기반해 평가한다고 가정하겠다. ↩︎

  4. 앞서 설명한 람다 대수에서는 이를 쉽게 얻을 수 있다. 오메가(Ω\Omega)라고 부르는 표현식인 (λx→x x) (λx→x x)(\lambda x \to x\ x)\ (\lambda x \to x\ x)의 평가는 값에 의한 호출을 따르든 이름에 의한 호출을 따르든 종료되지 않는다. ↩︎

  5. 바로 이 함수 호출을 값 밀기에 기반해 해석하는 데에서 CBPV의 이름이 유래했다. ↩︎

Read more →
13
3
0

洪 民憙 (Hong Minhee) replied to the below article:

함수형 언어의 평가와 선택

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

함수형 언어(Functional Language)의 핵심

함수형 언어가 점점 많은 매체에 노출되고, 더 많은 언어들이 함수형 언어의 특징을 하나 둘 받아들이고 있다. 함수형 언어, 적어도 그 특징이 점점 대세가 되고 있다는 이야기이다. 하지만, 함수형 언어가 대체 무엇이란 말인가? 무엇인지도 모르는 것이 대세가 된다고 할 수는 없지 않은가?

함수형 언어란 아주 단순히 말해서 함수가 표현식[1]인 언어를 말한다. 다른 말로는 함수가 이기 때문에 다른 함수를 호출해서 함수를 얻어내거나 함수의 인자로 함수를 넘길 수 있는 언어를 말한다. 그렇다면 이 단순화된 핵심만을 포함하는 언어로 함수형 언어의 핵심을 이해할 수 있지 않을까? 이게 바로 람다 대수(Lambda Calculus)의 역할이다.[2]

람다 대수는 딱 세 종류의 표현식만을 가지고 있다.

  1. 변수 (xx, yy, …\ldots)
  2. 매개변수 xx에 인자를 받아 한 표현식 MM(함수의 몸체)을 계산하는 함수 (λx→M\lambda x\to M)
  3. 어떤 표현식 LL의 결과 함수를 인자 NN으로 호출 (L NL\ N)

이후의 설명에서는 MMNN, 그리고 LL이라는 이름을 임의의 표현식을 나타내기 위해 사용할 것이다. 람다 대수가 어떤 것들을 표현할 수 있는가? 앞에서 말했듯이 람다 대수는 함수의 인자와 함수 호출의 결과가 모두 함수인 표현식을 포함한다. 예를 들어 λx→(λy→y)\lambda x \to (\lambda y \to y) 는 매개변수 xx에 인자를 받아 함수 λy→y\lambda y \to y를 되돌려주는 함수이고, λx→(x (λy→y))\lambda x \to (x\ (\lambda y \to y))는 매개변수 xx에 함수인 인자를 받아 그 함수를 (λy→y\lambda y \to y를 인자로 사용하여) 호출하는 함수이다.

람다 대수(Lambda Calculus)의 평가(Evaluation)

이제 문제는

그래서 람다 대수의 표현식이 하는 일이 뭔데?

이다. 위의 표현식에 대한 소개는 산수로 말하자면 x+yx + y와 같이 연산자(++)와 연산항(xxyy)로부터 얻어지는 문법만을 설명하고 있고, 3+53 + 5와 같은 구체적인 표현식을 계산해서 88이라는 결과 값을 내놓는 방식을 설명하고 있지 않다. 이런 표현식으로부터 값을 얻어내는 것을 언어의 "평가 절차"("Evaluation Procedure")라고 한다. 람다 대수의 평가 절차를 설명하는 것은 어렵지 않다. 적어도 표면적으로는 말이다.

  • 함수는 이미 값이다.
  • 함수 λx→M\lambda x \to MNN으로 호출하면 MM에 등장하는 모든 xxNN으로 치환(Substitute)하고 결과 표현식의 평가를 계속한다.

이는 겉으로 보기에는 말이 되는 설명처럼 보인다. 하지만 이 설명을 실제로 해석기(Interpreter)로 구현하려고 시도한다면 이 설명이 사실 여러 세부사항을 무시하고 있다는 점을 깨닫게 될 것이다.

  1. 함수 호출 L NL\ N에서 LL이 (아직) λx→M\lambda x \to M 꼴이 아닐 때는 어떻게 해야하지?
  2. 함수 호출 (λx→M) N(\lambda x \to M)\ N에서 NN을 먼저 평가하는 게 낫지 않나? xxMM에 여러번 등장한다면 NN을 여러번 평가해야 할텐데?

첫번째 문제는 비교적 간단히 해결할 수 있다. LL을 먼저 평가해서 λx→M\lambda x \to M 꼴의 결과 값을 얻어낸 뒤에 호출을 실행하면 되기 때문이다. 반면에 두번째 질문은 좀 더 미묘한 문제를 가지고 있다. 함수 호출의 평가에서 발생하는 이 문제에 구체적인 답을 하기 위해서는 값에 의한 호출(Call-By-Value, CBV)와 이름에 의한 호출(Call-By-Name, CBN)이 무엇인지 이해해야 한다.

값에 의한 호출(Call-By-Value)? 이름에 의한 호출(Call-By-Name)?

앞에서 말한 함수 호출에서부터 발생하는 문제는 사실 함수형 언어에서만 발생하는 문제는 아니다. C와 같은 명령형 언어에서도 함수를 호출할 때 인자를 먼저 평가해야하는지를 결정해야하기 때문이다. 즉 이 문제는 함수를 가지고 있고 함수를 호출해야하는 모든 언어들이 가지고 있는 문제이다.

그렇다면 이 일반적인 문제를 어떻게 해결하는가? 대부분의 언어가 취하는 가장 대표적인 방식은 "값에 의한 호출"("Call-By-Value", "CBV")이라고 한다. 이 함수 호출 평가 절차에서는 함수의 몸체에 인자를 치환하기 전에[3] 인자를 먼저 평가한다. 이 방식을 사용하면 인자를 여러번 평가해야하는 상황을 피할 수 있다.

또 다른 방식은 "이름에 의한 호출"("Call-By-Name", "CBN")이라고 한다. 이 방식에서는 함수의 몸체에 인자를 우선 치환한 후 몸체를 평가한다. 몇몇 언어의 매크로(Macro)와 같은 기능이 이 방식을 사용한다. 얼핏 보기에는 CBN은 장점이 없어보인다. 그러나 함수가 인자를 사용하지 않을 경우는 CBN이 장점을 가진다는 것을 볼 수 있다. 극단적으로 평가가 종료되지 않는 표현식(Non-terminating expression)이 있다면[4] CBV는 종료하지 않고 CBN만이 종료하는 경우가 있음을 다음 예시를 통해 살펴보자. 표현식 (λx→(λy→y)) N(\lambda x \to (\lambda y \to y))\ N이 있다고 할 때, NN이 평가가 종료되지 않는 표현식이라고 하자. 이 경우 CBV를 따른다면 종료하지 않는 NN 평가를 먼저 수행하느라 이 표현식의 값을 얻어낼 수 없지만, CBN을 따른다면 λy→y\lambda y \to y라는 값을 손쉽게 얻어낼 수 있다. 바로 이런 상황 때문에

CBN은 CBV보다 일반적으로 더 많은 표현식들을 평가할 수 있다

고 말한다.

모호한 선택을 피하는 방법

두 방식의 장점을 모두 가질 수는 없을까? 다시 말해서, 어떤 상황에서는 이름에 의한 호출을 사용하고, 어떤 상황에서는 값에 의한 호출을 사용할 수 없을까? 이 질문에 답한 수많은 선구자들 가운데 폴 블레인 레비(Paul Blain Levy)가 내놓은 답인 "값 밀기에 의한 호출"("Call-By-Push-Value", "CBPV")은 함수형 언어의 평가를 기계 수준(Machine level)에서 이해하는데에 있어 강력한 도구를 제공한다. CBPV는 우선 "계산"("Computation")과 "값"("Value")을 구분한다.

  • 계산 MM, NN, LL, …\ldots = 함수 λx→M\lambda x \to M 또는 함수 호출 L VL\ V
  • VV, UU, WW, …\ldots = 변수 xx

잠깐, 앞서서 함수형 언어에서 함수는 값이라고 하지 않았던가? 이는 값 밀기에 의한 호출에서 함수와 함수 호출을 종전과 전혀 다르게 이해하기 때문이다. 함수 λx→M\lambda x \to M는 스택(Stack)에서 값을 빼내어(Pop) xx라는 이름을 붙인 후 MM을 평가하는 것이고, 함수 호출 L VL\ V는 스택에 값 VV를 밀어넣고(Push)[5] LL을 평가하는 것이다. 따라서 함수 λx→M\lambda x \to M는 평가의 결과가 아닌 추가적인 평가가 가능한 표현식이 된다. 이 구분을 간결하게 설명하는 것이 다음의 CBPV 표어이다.

값은 "~인 것"이다. 계산은 "~하는 것"이다.

그렇지만 함수형 언어이기 위해서는 함수를 값으로 취급할 수 있어야 한다고 했지 않은가? 그렇다. 이를 위해 CBPV는

계산을 강제한다면(force\mathbf{force}) 계산 MM를 하는 지연된 계산인 값 thunk(M)\mathbf{thunk}(M)

을 추가로 제공한다. 이 둘 (force(V)\mathbf{force}(V)thunk(M)\mathbf{thunk}(M))을 다음과 같이 문법에 추가할 수 있다.

  • 계산 = λx→M\lambda x \to M 또는 L VL\ V 또는 force(V)\mathbf{force}(V)
  • 값 = xx 또는 thunk(M)\mathbf{thunk}(M)

CBPV를 완성하기 위해 필요한 마지막 조각은 계산을 끝내는 법이다. 현재까지 설명한 λx→M\lambda x \to ML VL\ V 그리고 force(V)\mathbf{force}(V) 는 모두 다음 계산을 이어서 하는 표현식이고, 계산을 끝내는 방법을 제공하지는 않는다. 예를 들어 λx→M\lambda x \to M의 평가는 스택에서 값을 빼내고 계산 MM의 평가를 이어한다. 그렇다면 계산의 끝은 무엇인가? 결과 값을 제공하는 것이다. 이를 위해 return(V)\mathbf{return}(V)를 계산에 추가하고, 이 결과 값을 사용할 수 있도록 M to x→NM\ \mathbf{to}\ x \to N (계산 MM을 평가한 결과 값을 xx라고 할 때 계산 NN을 평가하는 계산) 또한 계산에 추가하면 다음의 완성된 CBPV를 얻는다.

  • 계산 = λx→M\lambda x \to M 또는 L VL\ V 또는 force(V)\mathbf{force}(V) 또는 return(V)\mathbf{return}(V) 또는 M to x→NM\ \mathtt{\mathbf{to}}\ x \to N
  • 값 = xx 또는 thunk(M)\mathbf{thunk}(M)

이제 CBPV를 얻었으니 원래의 목표로 돌아가보자. 어떻게 CBV 호출과 CBN 호출을 CBPV로 설명할 수 있을까?

  • CBV 함수 λx→M\lambda x \to M와 호출 L NL\ N이 있다면, 이를 return(thunk(λx→M))\mathbf{return}{(\mathbf{thunk}(\lambda x \to M))}L to x→N to y→force(x) yL\ \mathbf{to}\ x \to N\ \mathbf{to}\ y \to \mathbf{force}(x)\ y로 표현할 수 있다. 즉, CBPV의 관점에서 CBV의 함수는 지연된 원래 계산 λx→M\lambda x \to M을 값으로 되돌려주는 계산으로 이해할 수 있고, 함수 호출 L NL\ N은 함수 부분 LL을 먼저 평가하고 NN을 평가한 뒤 NN의 계산 결과 yy를 스택에 밀어넣고 지연된 계산인 함수 부분 xx의 계산을 강제하는(force(x)\mathbf{force}(x)) 것으로 이해할 수 있다.
  • CBN 함수 λx→M\lambda x \to M와 호출 L NL\ N이 있다면, 이를 λx→M\lambda x \to M(단, 변수 xx의 모든 사용을 force(x)\mathbf{force}(x)로 치환함)과 L thunk(N)L\ \mathbf{thunk}(N)로 표현할 수 있다. 즉, CBPV의 관점에서 함수 호출은 L NL\ N은 지연된 NN을 스택에 밀어넣은 뒤 LL의 계산을 이어가는 것으로 볼 수 있다. 이 지연된 NN은 이후에 스택에서 빼내어져 어떤 이름 xx가 붙은 뒤, 이 변수가 사용될 때에야 비로소 계산된다.

다소 설명이 복잡할 수 있으나, 단순하게 말해서 CBPV는 CBV에 따른 상세한 평가 순서와 CBN 따른 상세한 평가 순서를 세부적으로 설명할 수 있는 충분한 기능을 모두 갖추고 있으며, 이를 통해 CBV 함수 호출과 CBN 함수 호출을 모두 설명할 수 있다는 이야기이다.

기계 수준(Machine level)에서의 Call-By-Push-Value의 장점

앞에서는 CBPV가 CBV와 CBN를 모두 설명할 수 있음을 다뤘다. 그러나 CBPV는 프로그래머(Programmer)가 직접 사용하기에는 과도하게 자세한 세부사항들을 포함하고 있기에, 프로그래머가 직접 CBPV를 써서 CBV와 CBN의 구분을 조율하기에는 적합하지 않다. 그렇다면 어느 수준에서 CBV와 CBN을 혼합해 사용할 때 도움을 줄 수 있을까? 바로 람다 대수를 기계 수준으로 컴파일(Compile)할 때이다. 이때는 CBPV가 가진 자세한 세부사항의 표현력이 굉장히 유용해진다.

예를 들어 람다 대수를 기계 수준으로 변환할 때 흔히 필요한 것 중 하나인 항수 분석(Arity analysis)에 대해 이야기해보자. 항수 분석은 함수가 하나의 인자를 받은 뒤 실행되어야 하는지, 혹은 두 인자를 모두 받아 실행되어야 하는지 등을 확인하여 이후에 그에 걸맞는 최적화된 기계어(Machine language)를 생성할 수 있게 도와주는 분석 작업이다. 평범한 람다 대수에서는 항수 분석의 결과를 직접적으로 표현하기 어렵다. 예를 들어 람다 대수의 λx→(λy→y)\lambda x \to (\lambda y \to y)의 경우 이 함수가 xxyy를 모두 받아 yy를 되돌려주는 함수인지 (항수가 2인 함수인지), 혹은 xx를 받아 λy→y\lambda y \to y라는 함수를 되돌려주는 함수인지 (항수가 1인 함수인지) 구분할 수 없다. 그러나 이를 CBPV로 변환한 λx→(λy→return(y))\lambda x \to (\lambda y \to \mathtt{return}(y))λx→return(thunk(λy→return(y)))\lambda x \to \mathtt{return}(\mathtt{thunk}(\lambda y \to \mathtt{return}(y)))는 각각이 무엇을 뜻하는지 분명히 이해할 수 있다.

  • λx→(λy→return(y))\lambda x \to (\lambda y \to \mathtt{return}(y))는 두 변수 xxyy를 스택에서 빼낸 뒤 yy의 값을 되돌려주는 함수(항수가 2인 함수)이다.
  • λx→return(thunk(λy→return(y)))\lambda x \to \mathtt{return}(\mathtt{thunk}(\lambda y \to \mathtt{return}(y)))는 변수 xx를 스택에서 빼낸 뒤 지연된 계산 λy→return(y)\lambda y \to \mathtt{return}(y)를 돌려주는 함수(항수가 1인 함수)이다.

이런 장점을 바탕으로 CBPV를 더 발전시킨 "언박싱한 값에 의한 호출"("Call-By-Unboxed-Value")을 GHC 컴파일러의 중간 언어(Intermediate language)로 구현하는 것에 대한 논의가 현재 진행되고 있으며 앞으로 더 많은 함수형 컴파일러들이 관련된 중간 언어를 채용하기 시작할 것으로 보인다.

마치며

이 글에서는 함수형 언어의 핵인 람다 대수를 간단히 설명하고 람다 대수를 평가하는 방법에 대해서 다루어보았다. 특히 그 중 값 밀기에 의한 평가(Call-By-Push-Value, CBPV)가 무엇이며 CBPV가 다른 대표적인 두 방법(CBV, CBN)을 어떻게 표현할 수 있는지, 그리고 CBPV의 장점이 무엇인지에 대해서도 다루어 보았다. 이 글에서 미처 다루지 못한 중요한 주제는 CBPV를 기계에 가까운 언어로 번역해보는 것이다. 여기에서는 글이 너무 복잡해지는 것을 피하기 위해 제했으나, CBPV의 장점에서 살펴봤듯 이는 CBPV에 있어 핵심 주제 중 하나이기 때문에 이후에 다른 글을 통해서라도 이 주제를 소개할 기회를 가지고자 한다. 이 글이 CBPV에 대한 친절한 소개글이었기를 바라며 이만 줄이도록 하겠다.


  1. 결과 (Value)을 가지는 언어 표현을 말한다. 예를 들어 1+11 + 122라는 값을 가지는 표현식이지만 (JavaScript의) let x = 3;나 (Python의) def f(): ...은 그 자체로는 값이 없기 때문에 표현식이 아니다. ↩︎

  2. 다만 실제 역사에서는 람다 대수의 이해와 발견이 함수형 언어의 개발보다 먼저 이루어졌다. 이런 역사적 관점에서는 (이미 많은 수학자들이 이해하고 있던) 람다 대수에 여러 기능을 추가한 것이 바로 함수형 언어라고 볼 수 있다. ↩︎

  3. 프로그래밍 언어(Programming Language)는 실제로는 치환을 사용하지 않고 환경(Environment)을 사용하는 경우가 더 많지만 설명의 편의를 위해 다른 언어들 또한 환경 대신 치환에 기반해 평가한다고 가정하겠다. ↩︎

  4. 앞서 설명한 람다 대수에서는 이를 쉽게 얻을 수 있다. 오메가(Ω\Omega)라고 부르는 표현식인 (λx→x x) (λx→x x)(\lambda x \to x\ x)\ (\lambda x \to x\ x)의 평가는 값에 의한 호출을 따르든 이름에 의한 호출을 따르든 종료되지 않는다. ↩︎

  5. 바로 이 함수 호출을 값 밀기에 기반해 해석하는 데에서 CBPV의 이름이 유래했다. ↩︎

Read more →
13
3
0
1

Jira, Linear 등 일정 관리 앱이 풀어야할 가장 어려운 문제는, 사용자 중 상당수는 애초에 일정 관리를 하기 싫어하는 사람이라는 것이다. 안타깝게도 나도 거기 포함되는데, 문제는 그런 사람일 수록 일정 관리가 꼭 필요하다. 나중에 프로젝트가 복잡해지면 일정 관리 앱을 켜는 거 자체를 꺼리게 된다. 이걸 어쩌면 좋지.

1

lionhairdino shared the below article:

같은 것을 알아내는 방법

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

이 글은 일상적인 질문에서부터 컴퓨터 과학의 핵심 문제에 이르기까지, '같음'이라는 개념이 어떻게 적용되고 해석되는지를 탐구합니다. 특히, 두 프로그램이 '같은지'를 판정하는 문제에 초점을 맞춰, 문법적 비교와 $\beta$ 동등성이라는 두 가지 접근 방식을 소개합니다. 문법적 비교는 단순하지만 제한적이며, $\beta$ 동등성은 프로그램의 실행을 고려하지만, 계산 복잡성으로 인해 적용이 어렵습니다. 이러한 어려움에도 불구하고, 의존 형 이론에서의 형 검사(변환 검사)는 $\beta$ 동등성이 유용하게 활용될 수 있는 중요한 사례임을 설명합니다. 이 글은 '같음'의 개념이 프로그래밍과 타입 이론에서 어떻게 중요한 역할을 하는지, 그리고 이 개념을 올바르게 이해하고 구현하는 것이 왜 중요한지를 강조하며 마무리됩니다.

Read more →
5
2
2
0
0

디지털 접근성에 관심 있는 분들이 계시려나...

구린 영어 실력에 LLM을 빌어서 일단은 혼자 하고 있지만...

예전처럼 평가 중심이 아닌 구현 방법 중심의 글을 좀 생산하고 싶다고...

일단 계획은 이런데... 같이 할 사람 있으면 좋겠다고 생각합니다아아...

https://a11ykr.github.io/docs/

0
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

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

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

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

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

닉스(Nix)를 써보려고 Nix Pills라는 글을 읽기 시작했다. 화면에 Hello, world!를 출력하는 패키지 hello를 닉스로 설치하는 방법이 나오더라.

nix-env -i hello

저런 하찮은 프로그램이 패키지로 존재하는지 처음 알았다.

혹시 해키지에도 저런 게 있는지 검색을 해봤는데 있더라.[1] 2010년에 Simon Marlow가 업로드했다. 그런데 특이한 점이 이 패키지의 소스 코드 저장소 주소가 darcs.haskell.org라는 것이다. darcs를 호스팅하는 곳은 hub.darcs.net만 있는 줄 알았는데 haskell.org에도 있었구나⋯ 그런데 이 사이트 UI도 아예 없고 그냥 디렉터리 리스팅이 나오는데 hub.darcs.net으로 이전하시면 안 되려나.


  1. https://hackage.haskell.org/package/hello-1.0.0.2 ↩︎

0
0
0
1
0

조던 엘렌버그라는 수학자가 쓴 '틀리지 않는 법'이란 책이 있는데, 재밌고 읽어볼만하다.

그 책에서 컴퓨터가 수학자가 하던일을 할수 있게되면 어떡하냐에 대해 저자의 해결책을 제시하는데 원래 하던일을 컴퓨터한테 맡기고 수학자들은 더 고차원적인 일을 하면 된다고 한다. 한 10년 전에 읽었을때도 그냥 대책없고 나이브한 생각으로 보였다. 근데 요즘은 (실제로 어떻게 될지랑은 상관없이) 저런 나이브한 마음가짐을 일부러라도 가지고 일해야 뭐라도 해낼듯.

0

해키지(Hackage)[1]에 패키지를 업로드하면 자동으로 빌드, 문서 생성, 테스트가 진행된다. 그런데 이게 시간이 좀 걸린다.(체감상 10분 정도) 이 과정이 자동으로 완료되기 전에 참지 못하고 수동으로 문서를 업로드하면 자동으로 진행되던 것들이 모두 중단된다. https://github.com/haskell/hackage-server/issues/1376


  1. 하스켈 패키지 저장소 ↩︎

0
0
0

lionhairdino shared the below article:

스태키지 큐레이션이 성공할 수 있었던 것은 하스켈이라는 언어와 생태계의 특징도 컸습니다.

juxtapose @xt@hackers.pub

인용: https://hackers.pub/@bgl/0195f0eb-88dd-77e3-a864-f0371e85b270

스태키지(Stackage)는 하스켈이 (의외로) 성공하여 해키지(Hackage)가 거대해지자, 그 거대함 때문에 발생하는 불편을 해소하는 한 방책으로 고안되었습니다. 그런데 당시에 해키지만큼 거대한 생태계를 갖추고 있으면서 동시에 "컴파일이 성공한다면 실행도 아마 성공할 것"이라는 훌륭한 속성을 갖는 언어는 달리 없었죠. 러스트가 있지 않으냐? 스태키지가 처음 나온 게 2012년입니다. 러스트는 아직 crates.io 도 자리잡기 전이었죠. (사실 이 시점의 러스트는 지금과는 언어 자체가 많이 다른 언어였고요.)

하스켈의 패키지 버저닝 정책에 따르면, 후방호환성 깨지는 변경은 반드시 메이저 버전을 올려야 하고, 마이너 버전만 올리는 변경은 후방호환성 유지될 때에만 가능합니다. 이런 정책 당연히 좋지만, 사람이 내용을 잘 숙지하고 지켜야 의미가 있습니다. 후방호환성을 깨면서 마이너 버전만 올리는 실수는 어떤 개발자든 할 수 있죠.

그런데 하스켈의 경우, 인간이 실수해도 기계가 잡아 줄 여지가 처음부터 매우 큰 언어이고, 예를 들어 어떤 함수가 핵미사일을 발사할 수 있는지 아닌지를 함수 실행 없이도 식별할 수 있는 언어라고들 하죠. 하스켈의 마지막 표준이 2010년에 나왔으니 2010년을 기준으로 하면, 당시 하스켈이 제공하는 "컴파일 시간 보장"의 범위는 그야말로 독보적이었습니다. (하스켈보다 더 강한 보장을 제공하는 언어들은 있었지만, 그만한 라이브러리 에코시스템이 없었고요.)

그래서 스태키지라는 모형이 의미가 있었습니다. A라는 패키지의 새 마이너 버전이 해키지에 올라오면, 스태키지에서 자동으로 가져갑니다. 스태키지는 같은 큐레이션에 포함된 다른 패키지들 중 A에 의존하는 패키지들을 추리고, 얘네한테 A의 새 버전을 먹여도 빌드가 잘 되는지 검사합니다. 이들 중 하나라도 깨지면? A 패키지는 해키지에서는 버전이 올랐으나, 스태키지에서는 버전이 오르지 않게 됩니다. 그리고 A 패키지의 제공자에게 자동으로 깃허브 멘션 알림이 갑니다!

("패키지 저자"와 "패키지를 스태키지에 제공하는 제공자"가 같은 사람이 아니어도 된다는 점도, 노동력의 효과적 분담에 한몫했습니다.)

이 모든 과정이 자동화되어 있는데, 이것만으로도 99.99%의 호환성 문제가 사라지고, 그러면서도 웬만한 라이브러리들은 충분히 최신 버전으로 쓸 수 있습니다. LTS와 나이틀리가 구분되어 있는 것도, LTS가 GHC 버전에 대응하여 여러 버전이 유지되는 것도, 실제 개발에서 아주 편리하고요.

스태키지가 개쩌는 부분은 "버저닝 정책에 완벽하게 부합하는데도 현실적으로 후방호환성 파괴를 일으키는" 변경점들도 잡아낸다는 것입니다. 아주 단순한 예시로 "많이 쓰이는 이름"이 있습니다. 예를 들어 어떤 라이브러리가 아주 널리 쓰이는데, 제공하는 네임 바인딩은 몇 개 안 되고, 그래서 대부분의 사용자가 그걸 그냥 전역 네임스페이스에 다 반입해서 쓴다고 칩시다. 어느 날 이 라이브러리가 process 라든지 f 같은 새 네임을 추가 제공하기 시작하면? 정책 규범에 따르면, 이것도 마이너 버전만 올려도 되는 변경점이 맞습니다. 하지만 현실에서는 많은 패키지들을 박살내겠죠. 언어를 막론하고 있을 수 있는 일인데, 이런 것들까지 스태키지에서 아주 높은 확률로 다 잡힙니다.

그리고... 이런 게 잘 된다는 것은 언어 그 자체의 특성도 있지만, 생태계 전체의 문화적인 특성도 있는데요. 하스켈도 라이브러리 제작자가 충분히 악독하다면, 컴파일러에게 안 잡히면서 인류문명멸망시키는 코드 변경을 얼마든지 슬쩍 끼워넣을 수 있습니다. 악의가 아니더라도 부주의로 후방호환성을 깰 수 있고요. 그런데 하스켈은 대부분의 라이브러리 설계자들이 "되도록 많은 것을 컴파일 시간에 잡고 싶다"라는 명확한 욕망으로 설계를 하는 경향이 뚜렷합니다. 그래서 호환성 문제는 웬만하면 스태키지 선에서 잡히고, 스태키지 큐레이션은 지난 10년 동안 실무상 아주 유용한 도구로 기능해 온 것이죠.

어지간하면 큐레이션만 잘 고르고 잘 갱신하면 되고, 종속성 목록에는 mypkg >= 2.1.1 && < 2.1.2 이런 거 하나도 관리 안 하고 그냥 mypkg 라고만 써도 된다는 것이, 솔직히 개짱편합니다. 다행히 지난 10년 동안 "문제의 소지는 컴파일 시간에 검출하는 게 좋다"라는 생각이 더 널리 받아들여져서, 다른 언어들도 이런 접근을 더 시도할 여지가 생긴 것 같군요.

Read more →
2