2P by GN⁺ | ★ favorite | 댓글 1개
  • Hurl은 일반적인 분기나 반복 대신 예외 처리만으로 제어 흐름을 구성하는 실험적 프로그래밍 언어임
  • Nicole Tietz-Sokolskaya와 Recurse Center 친구들의 대화에서 시작됐고, 사이트는 사용법 문서와 예제, 디버깅 안내, 질문 답변을 함께 제공함
  • 소개 페이지의 추천사는 “monstrosity is beautiful”, “Certified unhinged™”처럼 의도적으로 농담성 분위기를 전면에 둠
  • Hurl과 사이트 소스 코드는 공개되어 있지만, 이메일 패치를 보내려면 패치 권리 양도가 필요함
  • 라이선스는 AGPL-3.0, GAL-1.0, 상업 라이선스 중 하나를 선택하는 방식으로 제공됨

예외 처리만 남긴 언어 실험

  • Hurl은 한 가지 목적을 위해 만들어짐: 예외 처리 기반 제어 흐름만으로 구성된 언어가 가능한지 탐구하는 것
  • 아이디어는 Nicole Tietz-Sokolskaya와 Recurse Center 친구들의 대화에서 나왔으며, 친구들의 신원은 “품위를 위해” 공개하지 않음
  • 사이트는 Hurl 사용법 문서와 예제, 디버깅 안내, 질문 답변을 제공함

농담처럼 보이지만 실제로 공개된 프로젝트

  • 소개 페이지에는 Hurl의 성격을 드러내는 추천사가 실려 있음
    • “This monstrosity is beautiful, and I must never touch it”
    • “Certified unhinged™!”
    • “is "🤮" an available quote?”
  • 추가 추천사를 싣고 싶다면 Nicole에게 이메일을 보내야 하며, 인용 포함에 대한 명시적 동의가 필요함

소스 코드와 라이선스 조건

  • Hurl 언어와 이 사이트의 소스 코드는 Hurl's repo에 공개되어 있음
  • 버그나 오류를 찾으면 이메일 패치를 보낼 수 있지만, 패치에 대한 모든 권리를 넘겨야 함
    • 재라이선스와 상업 라이선스 가능성을 유지하기 위한 조건임
  • 프로젝트는 다음 세 가지 라이선스 중 하나로 사용할 수 있음
    • AGPL-3.0

      • GAL-1.0: Gay Agenda License
      • 상업 라이선스
      • 라이선스 검토 과정에서 joke licensesunfortunate licenses도 고려했지만, 최종적으로 위 세 라이선스를 사용함

댓글과 토론

Hacker News 의견들
  • 프로그래밍 언어를 설계한다면 include/import에 네임스페이스를 강제하고, 가능하면 최상위 레벨 부수 효과도 막는 게 좋겠음
    let foo = include "lib/foo.hurl"처럼 받고 foo.init()을 호출하면 추론하기 훨씬 쉬움
    반대로 include "lib/foo.hurl" // side effects 뒤에 baz(buz)가 나오면 함수와 변수가 표준 라이브러리인지, 어디선가 include된 것인지 알기 어려움

    • 명시적 이름 바인딩이 없는 경우에는 import 문과 네임스페이스가 일치하도록 강제하는 편이 좋음
      import "foo/bar"foo.* 또는 bar.*를 사용할 수 있게 해야지 bazz.*가 나오면 안 됨. Go가 딱 떠오름
    • 동의하지 않는 건 아니지만, 업무에서 쓰는 IntelliJ는 어떤 참조가 어디서 import됐는지 명확히 보여주고 단축키로 이동도 가능함
      VSCode도 플러그인과 LSP로 비슷하게 하긴 하지만 훨씬 못함. 코드 탐색이 너무 느려서 VSCode로는 일할 수 없음
      이런 제안은 그런 도구가 없을 때만 유용한 건지 궁금함. 적어도 프로 환경에서는 그런 도구 없이 산다는 게 불가능해 보임
    • 이렇게 하면 foo.init()매개변수를 넘길 수 있고, 맨몸 import로는 그게 불가능함
    • 흐름 제어가 예외 중심인 언어라면, 이미 “추론하기 쉬움”이라는 배는 떠난 것 같음
      그렇다고 이 프로젝트가 무가치하다는 뜻은 아니고, 오히려 예술 작품이라고 봄
    • 100% 동의함
      Ruby를 fork해서 require가 심볼 테이블을 덮어쓰지 않게 만들어본 적이 있는데, Ruby 생태계가 공유 전역 가변 상태에 지나치게 의존하는 것 같아 결국 Ruby 자체에 흥미를 잃었음
  • 예외는 호출자와 피호출자 사이의 계약을 파악하기 어렵게 만들고 코드 결합도도 높여서 늘 별로였음
    Go나 Rust처럼 반환값으로 처리하는 방식을 선호함. 언어를 대충 훑어봤을 때 이 문제를 해결하는 요소가 있는지는 잘 모르겠음
    IDE가 함수의 모든 잡히지 않은 예외를 동적으로 알아내고, 예외가 던져질 수 있는 위치로 점프하게 해준다면 이런 모델도 괜찮을 수 있음. 다만 결합도는 어떻게 다룰지 모르겠고, 제어 흐름 그래프가 극도로 불안정해질 것 같음

    • Java에서 IntelliJ가 바로 그런 일을 해줌. 예외를 던지는 함수가 있고 프로젝트 어딘가의 호출자에서 잡히지 않으면 문제로 표시하며, 구현이나 호출 위치로 쉽게 이동 가능함
      다만 Java에서는 RuntimeException을 상속하지 않는 한 함수가 던질 수 있는 예외가 함수 시그니처의 일부임. 그런 경우 시그니처에 추가하지 않고 예외를 던지면 컴파일되지 않음
      Java의 조건이 IDE가 잡히지 않은 예외를 보고하기 훨씬 쉽게 만들지만, 런타임 예외가 아닌 경우라면 정적 분석으로 풀 수 있는 문제임
      반면 표준화된 Ok/Err 래핑 값을 반환하는 방식은 도구 지원과 개발자 편의 양쪽에서 더 단순해 보임
    • 예외를 던지는 것과 예외를 변수로 반환하는 것 사이에는 말 그대로 아무 차이도 없음
      차이는 예외 전달 방식에서는 컴파일러가 해줄 상용구 코드를 직접 손으로 써야 한다는 점뿐임
      2024년에 정상적으로 작동하는 머리를 가진 사람이 왜 그걸 손으로 하고 싶어 하는지 도저히 이해가 안 됨
    • Go가 오류 문맥을 자연스럽게 먹어치우지만 않았다면 더 좋아했을 것 같음
      써본 기간 동안에는 디버거 없이 근본 원인을 찾기가 훨씬 더 고통스러웠음
  • toss 예시는 “함수 밖으로 여러 값을 전달하는 데 주로 쓰며 꼭 필요하진 않지만 귀엽다”고 되어 있는데, 쓸모없기는커녕 재개 가능한 생성기를 구현한 것임
    물론 즉시 재개하는 것 말고 다른 일을 하게 만들려면 꽤 흥미로울 수 있음. 코드베이스 전체를 toss의 안쪽-바깥쪽 스택으로 구성하기만 하면 됨

    • 정확히 같지는 않음. 재개 가능한 생성기는 프로그램의 나중 어떤 지점에서도 재개할 수 있지만, 여기서는 return이 처리기 쪽에 어휘적으로 스코프되어야 함
      예를 들어 Python에서는 어디서든 next()를 호출할 수 있음
      이건 부수 채널로 콜백을 넘기는 것에 더 가까움. toss가 그 콜백을 호출하고, return은 말 그대로 거기서 반환하는 것임
    • 읽고 나서 처음 든 생각도 정확히 그거였음
      다른 언어라면 진짜 유용한 기능을, 여기서는 버려도 되는 귀여운 기능처럼 다루는 게 약간 농담인가 싶기도 함
    • 스택 기반 이벤트 전파에 더 가까움
    • C#의 yield 같은 것인가?
    • 첫 생각도 같았음. 생성기가 있는 작고 멋진 언어라니 괜찮음
  • Hurl은 Smalltalk나 Common Lisp식 조건 시스템에 상당히 가까워 보임
    스택 풀기와 재개는 가능한 재시작 중 두 가지일 뿐임: https://gigamonkeys.com/book/beyond-exception-handling-condi...

  • 프로젝트 자체와는 별개로, 더 많은 것들이 도메인에 .wtf 확장자를 쓰면 세상이 더 나아질 거라고 굳게 믿음

  • 이는 대수적 효과의 약한 형태처럼 들리지만, 이런 언어와 그걸로 무엇을 할 수 있는지 보는 건 여전히 멋짐

    • 대수적 효과를 이해한 적은 없지만 Hurl 문서는 이해됨
      대수적 효과가 기본적으로 Hurl의 toss 키워드 같은 것이라면, 어떤 점에서 toss보다 더 강력한지 궁금함
  • 재미있는 사고 실험임
    예외를 정말 싫어하고, 예외가 없는 언어를 원함
    예외는 우리 시대의 goto
    Maybe/OptionEffect/Result가 있는 이상, 예외를 던지고 어디서 처리되는지 머릿속으로 추적해야 할 이유가 거의 없음
    대수적 효과가 더 인기를 얻고, 이미 너무 복잡한 프론트엔드 JS에도 영향을 줄까 봐 조금 걱정됨. 흐름 제어를 위해 “예외”를 던지는 방식을 장려하기 때문임
    이 모든 게 비동기/동기 색깔 문제를 피하려는 목적이라면, 개인적으로는 전혀 그럴 가치가 없음

  • 와, 이건 싫다. 그런데 이상하게도 거의 우아한 면이 있음
    머릿속으로 모델링하기는 매우 어렵지만 그래도 그렇음
    조금 더 진지하게 말하자면, 재개 가능한 예외와 재개 불가능한 예외에 대해 문법적으로 다른 catch 구문이 있었으면 좋겠음. 그러면 return이 가장 가까운 즉시 예외의 던진 쪽으로 제어 흐름을 돌려보내는지 아닌지에 대한 문법적 모호성이 사라짐
    그리고 표준 라이브러리는 일반적인 값 반환 함수로 도망치면 안 됨. 직접 만든 걸 먹어보니 속쓰리다고 해서 안 먹을 이유는 없음

    • 맞음. 다만 다른 대안으로, 값이 기대되는 위치에서 함수를 호출하면 문법이 자동으로 catch하게 만들 수도 있음
  • hurl된 것은 잡을 수 있지만 toss된 것은 잡을 수 없다고 이해한 게 맞나? 익숙해지는 데 시간이 좀 걸리겠음
    또 Hurl을 얼마나 많이 작성해야 사람들이 나를 tosser라고 부르기 시작할지도 걱정됨

  • Toss는 흥미로운 언어 구조처럼 들림. 스택을 걸어 올라가 예외 처리기를 찾고, 다시 원래 위치로 돌아와 아무 일도 없었던 것처럼 실행을 재개함
    이 구조로 런타임에 추가 동작을 주입할 수 있어 보임
    보통 객체지향 코드에서는 서비스 생성자를 통해 의존성 주입을 하지만, toss는 “toss 처리기”로 그걸 가능하게 하는 것인가?

    • Koka를 보면 됨. 대수적 효과 시스템이 있는 “진짜” 언어임
      https://koka-lang.github.io/koka/doc/book.html#why-handlers
    • Common Lisp의 조건 시스템과 꽤 비슷함
      많이 알지는 못하지만, 이런 식으로 런타임에 동작을 주입할 수 있게 해준다는 건 알고 있음
    • VB의 Resume Next와 비슷함