@iamuhunMu-Hun 무훈 님, 어서오세요! 오랜만입니다.
洪 民憙 (Hong Minhee)
@hongminhee@hackers.pub · 1014 following · 723 followers
Hi, I'm who's behind Fedify, Hollo, BotKit, and this website, Hackers' Pub! My main account is at
@hongminhee洪 民憙 (Hong Minhee)
.
Fedify, Hollo, BotKit, 그리고 보고 계신 이 사이트 Hackers' Pub을 만들고 있습니다. 제 메인 계정은:
@hongminhee洪 民憙 (Hong Minhee)
.
Fedify、Hollo、BotKit、そしてこのサイト、Hackers' Pubを作っています。私のメインアカウントは「
@hongminhee洪 民憙 (Hong Minhee)
」に。
Website
- hongminhee.org
GitHub
- @dahlia
Hollo
- @hongminhee@hollo.social
DEV
- @hongminhee
velog
- @hongminhee
Qiita
- @hongminhee
Zenn
- @hongminhee
Matrix
- @hongminhee:matrix.org
X
- @hongminhee
@hongminhee洪 民憙 (Hong Minhee) 초대 시스템 오픈을 계기로 더 풍성한 hackers.pub 이 되기를
응원합니다. 개발자들의 소소한 일상이나, 초심자를 비롯해 숙련자를 위한 정보들도 가득했으면 좋겠어요. 😂 그러기 위해 저도 부족하나마 힘을 보태보겠습니다. 😆
📢 Hackers' Pub 초대 시스템 오픈!
Hackers' Pub에 초대 시스템이 적용되었습니다. 이제 설정 → 초대 페이지에서 지인들을 초대할 수 있습니다.
주요 내용:
- 초대장 3장 지급: 기존 회원분들께 3장의 초대장이 지급되었습니다.
- 초대 방법: 설정 → 초대 페이지에서 초대 링크를 생성하여 공유하거나, 이메일 주소를 입력하여 초대할 수 있습니다.
- 추가 초대: 초대장은 향후 비정기적으로 추가될 예정입니다.
- 자동 팔로: 초대자와 피초대자는 자동으로 상호 팔로됩니다. (언팔로 가능.)
Hackers' Pub의 퀄리티를 유지하고, 더욱 풍성한 기술 논의를 위해 신중한 초대를 부탁드립니다.
궁금한 점이나 건의사항은 답글로 남겨주세요.
Hackers' Pub 커뮤니티 성장에 많은 참여 부탁드립니다!
📢 Hackers' Pub 招待システムオープン!
Hackers' Pub に招待システムが適用されました。これで設定→招待ページから知人を招待できます。
主な内容:
- 招待状3枚支給:既存会員の皆様には3枚の招待状が支給されました。
- 招待方法:設定→招待ページで招待リンクを作成して共有するか、メールアドレスを入力して招待できます。
- 追加招待:招待状は今後不定期に追加される予定です。
- 自動フォロー:招待者と被招待者は自動的に相互フォローされます。(フォロー解除可能)
Hackers' Pub のクオリティを維持し、より豊かな技術議論のために慎重な招待をお願いいたします。
ご不明な点やご要望は、この投稿への返信としてお寄せください。
Hackers' Pub コミュニティの成長にご協力をお願いいたします!
📢 Hackers' Pub 초대 시스템 오픈!
Hackers' Pub에 초대 시스템이 적용되었습니다. 이제 설정 → 초대 페이지에서 지인들을 초대할 수 있습니다.
주요 내용:
- 초대장 3장 지급: 기존 회원분들께 3장의 초대장이 지급되었습니다.
- 초대 방법: 설정 → 초대 페이지에서 초대 링크를 생성하여 공유하거나, 이메일 주소를 입력하여 초대할 수 있습니다.
- 추가 초대: 초대장은 향후 비정기적으로 추가될 예정입니다.
- 자동 팔로: 초대자와 피초대자는 자동으로 상호 팔로됩니다. (언팔로 가능.)
Hackers' Pub의 퀄리티를 유지하고, 더욱 풍성한 기술 논의를 위해 신중한 초대를 부탁드립니다.
궁금한 점이나 건의사항은 답글로 남겨주세요.
Hackers' Pub 커뮤니티 성장에 많은 참여 부탁드립니다!
초대 시스템 먼저 구현하기로! 초대장은 개수 제한이 있는 게 좋으려나, 그냥 무제한이 나으려나…? 🤔
일단 개수 제한 넣는 쪽으로 구현하고 있다. 초대장을 어느 시점에 누구에게 얼마나 줄지가 문제네.
@hongminhee洪 民憙 (Hong Minhee) 와, 기여하려고 각재고 있었는데 초고속이네요
@kodingwarriorJaeyeol Lee 꽤 초기 버전부터 있긴 있었어요 ㅎㅎㅎ
Lobsters 스타일의 초대 시스템을 먼저 구현할까, 아니면 인용을 먼저 구현할까? 🤔
초대 시스템 먼저 구현하기로! 초대장은 개수 제한이 있는 게 좋으려나, 그냥 무제한이 나으려나…? 🤔
@hongminhee洪 民憙 (Hong Minhee) ah I see, seems like it's `markdown-it-texmath` which uses KaTex?
I'm aware that KaTeX is lighter+faster but seen Math folks prefer MathJax. I've also read that MathML (by itself) is not good enough for some cases https://docs.mathjax.org/en/latest/output/mathml.html
@cheeaunChee Aun 🤔 Yeah, I once considered MathJax as well, but I decided to use MathML because the most of ActivityPub implementations simply don't allow JavaScript inside the content property—all JavaScript code are erased. It turns out Mastodon erases MathML too. 😅
@hongminhee洪 民憙 (Hong Minhee) converted to MathML server-side?
@cheeaunChee Aun 🤔 Yeah, exactly!
Hackers' Pub의 숨겨진 기능 중 하나. Markdown에서 TeX 수식을 쓸 수 있습니다. 다음과 같이 $ 사이에 TeX 수식을 넣으면 됩니다.
수식 테스트: $V_{sphere} = \frac{4}{3}\pi r^3$
아래처럼 표시됩니다.
수식 테스트:
Hollo에서도 잘 표시되는 반면, Mastodon에서는 역시나 제대로 표시되지 않네요… 😇
Hackers' Pub의 숨겨진 기능 중 하나. Markdown에서 TeX 수식을 쓸 수 있습니다. 다음과 같이 $ 사이에 TeX 수식을 넣으면 됩니다.
수식 테스트: $V_{sphere} = \frac{4}{3}\pi r^3$
아래처럼 표시됩니다.
수식 테스트:
https://github.com/dahlia/hackerspub/pull/12
해커스펍의 멘션 기능에 가독성 개선이 필요할 것 같아서 제안하는 느낌으로 PR은 올렸는데, 다른 분들도 어떤 의견을 가지고 계실지 모르겠다
@hongminhee洪 民憙 (Hong Minhee) Totally agreed. The main problem with the original incarnation of Java's checked exceptions was that it was quite verbose and didn't give an easy way to compress a set of error types (because there is no typedef or such in Java). Zig-style syntax could be easily adapted to checked exceptions for the reasonable convenience.
@yurume유루메 Yurume Java's checked exceptions were indeed too verbose, and the lack of type aliases (like typedef) made working with multiple exception types cumbersome. I'm not super familiar with Zig's error handling approach, but I'll definitely look into it—sounds like it might offer some interesting patterns that could have improved Java's implementation.
유루메 Yurume replied to the below article:
Revisiting Java's Checked Exceptions: An Underappreciated Type Safety Feature
洪 民憙 (Hong Minhee) @hongminhee@hackers.pub
Despite their bad reputation in the Java community, checked exceptions provide superior type safety comparable to Rust's Result<T, E> or Haskell's Either a b—we've been dismissing one of Java's best features all along.
Introduction
Few features in Java have been as consistently criticized as checked exceptions. Modern Java libraries and frameworks often go to great lengths to avoid them. Newer JVM languages like Kotlin have abandoned them entirely. Many experienced Java developers consider them a design mistake.
But what if this conventional wisdom is wrong? What if checked exceptions represent one of Java's most forward-thinking features?
In this post, I'll argue that Java's checked exceptions were ahead of their time, offering many of the same type safety benefits that are now celebrated in languages like Rust and Haskell. Rather than abandoning this feature, we should consider how to improve it to work better with modern Java's features.
Understanding Java's Exception Handling Model
To set the stage, let's review how Java's exception system works:
-
Unchecked exceptions (subclasses of
RuntimeExceptionorError): These don't need to be declared or caught. They typically represent programming errors (NullPointerException,IndexOutOfBoundsException) or unrecoverable conditions (OutOfMemoryError). -
Checked exceptions (subclasses of
Exceptionbut notRuntimeException): These must either be caught withtry/catchblocks or declared in the method signature withthrows. They represent recoverable conditions that are outside the normal flow of execution (IOException,SQLException).
Here's how this works in practice:
// Checked exception - compiler forces you to handle or declare it
public void readFile(String path) throws IOException {
Files.readAllLines(Path.of(path));
}
// Unchecked exception - no compiler enforcement
public void processArray(int[] array) {
int value = array[array.length + 1]; // May throw ArrayIndexOutOfBoundsException
}
The Type Safety Argument for Checked Exceptions
At their core, checked exceptions are a way of encoding potential failure modes into the type system via method signatures. This makes certain failure cases part of the API contract, forcing client code to explicitly handle these cases.
Consider this method signature:
public byte[] readFileContents(String filePath) throws IOException
The throws IOException clause tells us something critical: this method might fail in ways related to IO operations. The compiler ensures you can't simply ignore this fact. You must either:
- Handle the exception with a try-catch block
- Propagate it by declaring it in your own method signature
This type-level representation of potential failures aligns perfectly with principles of modern type-safe programming.
Automatic Propagation: A Hidden Advantage
One often overlooked advantage of Java's checked exceptions is their automatic propagation. Once you declare a method as throws IOException, any exception that occurs is automatically propagated to the caller without additional syntax.
Compare this with Rust, where you must use the ? operator every time you call a function that returns a Result:
// Rust requires explicit propagation with ? for each call
fn read_and_process(path: &str) -> Result<(), std::io::Error> {
let content = std::fs::read_to_string(path)?;
process_content(&content)?;
Ok(())
}
// Java automatically propagates exceptions once declared
void readAndProcess(String path) throws IOException {
String content = Files.readString(Path.of(path));
processContent(content); // If this throws IOException, it's automatically propagated
}
In complex methods with many potential failure points, Java's approach leads to cleaner code by eliminating the need for repetitive error propagation markers.
Modern Parallels: Result Types in Rust and Haskell
The approach of encoding failure possibilities in the type system has been adopted by many modern languages, most notably Rust with its Result<T, E> type and Haskell with its Either a b type.
In Rust:
fn read_file_contents(file_path: &str) -> Result<Vec<u8>, std::io::Error> {
std::fs::read(file_path)
}
When calling this function, you can't just ignore the potential for errors—you need to handle both the success case and the error case, often using the ? operator or pattern matching.
In Haskell:
readFileContents :: FilePath -> IO (Either IOException ByteString)
readFileContents path = try $ BS.readFile path
Again, the caller must explicitly deal with both possible outcomes.
This is fundamentally the same insight that motivated Java's checked exceptions: make failure handling explicit in the type system.
Valid Criticisms of Checked Exceptions
If checked exceptions are conceptually similar to these widely-praised error handling mechanisms, why have they fallen out of favor? There are several legitimate criticisms:
1. Excessive Boilerplate in the Call Chain
The most common complaint is the boilerplate required when propagating exceptions up the call stack:
void methodA() throws IOException {
methodB();
}
void methodB() throws IOException {
methodC();
}
void methodC() throws IOException {
// Actual code that might throw IOException
}
Every method in the chain must declare the same exception, creating repetitive code. While automatic propagation works well within a method, the explicit declaration in method signatures creates overhead.
2. Poor Integration with Functional Programming
Java 8 introduced lambdas and streams, but checked exceptions don't play well with them:
// Won't compile because map doesn't expect functions that throw checked exceptions
List<String> fileContents = filePaths.stream()
.map(path -> Files.readString(Path.of(path))) // Throws IOException
.collect(Collectors.toList());
This forces developers to use awkward workarounds:
List<String> fileContents = filePaths.stream()
.map(path -> {
try {
return Files.readString(Path.of(path));
} catch (IOException e) {
throw new UncheckedIOException(e); // Wrap in an unchecked exception
}
})
.collect(Collectors.toList());
3. Interface Evolution Problems
Adding a checked exception to an existing method breaks all implementing classes and calling code. This makes evolving interfaces over time difficult, especially for widely-used libraries and frameworks.
4. Catch-and-Ignore Anti-Pattern
The strictness of checked exceptions can lead to the worst possible outcome—developers simply catching and ignoring exceptions to make the compiler happy:
try {
// Code that might throw
} catch (Exception e) {
// Do nothing or just log
}
This is worse than having no exception checking at all because it provides a false sense of security.
Improving Checked Exceptions Without Abandoning Them
Rather than abandoning checked exceptions entirely, Java could enhance the existing system to address these legitimate concerns. Here are some potential improvements that preserve the type safety benefits while addressing the practical problems:
1. Allow lambdas to declare checked exceptions
One of the biggest pain points with checked exceptions today is their incompatibility with functional interfaces. Consider how much cleaner this would be:
// Current approach - forced to handle or wrap exceptions inline
List<String> contents = filePaths.stream()
.map(path -> {
try {
return Files.readString(Path.of(path));
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
// Potential future approach - lambdas can declare exceptions
List<String> contents = filePaths.stream()
.map((String path) throws IOException -> Files.readString(Path.of(path)))
.collect(Collectors.toList());
This would require updating functional interfaces to support exception declarations:
@FunctionalInterface
public interface Function<T, R, E extends Exception> {
R apply(T t) throws E;
}
2. Generic exception types in throws clauses
Another powerful enhancement would be allowing generic type parameters in throws clauses:
public <E extends Exception> void processWithException(Supplier<Void, E> supplier) throws E {
supplier.get();
}
This would enable much more flexible composition of methods that work with different exception types, bringing some of the flexibility of Rust's Result<T, E> to Java's existing exception system.
3. Better support for exception handling in functional contexts
Unlike Rust which requires the ? operator for error propagation, Java already automatically propagates checked exceptions when declared in the method signature. What Java needs instead is better support for checked exceptions in functional contexts:
// Current approach for handling exceptions in streams
List<String> contents = filePaths.stream()
.map(path -> {
try {
return Files.readString(Path.of(path));
} catch (IOException e) {
throw new RuntimeException(e); // Lose type information
}
})
.collect(Collectors.toList());
// Hypothetical improved API
List<String> contents = filePaths.stream()
.mapThrowing(path -> Files.readString(Path.of(path))) // Preserves checked exception
.onException(IOException.class, e -> logError(e))
.collect(Collectors.toList());
4. Integration with Optional<T> and Stream<T> APIs
The standard library could be enhanced to better support operations that might throw checked exceptions:
// Hypothetical API
Optional<String> content = Optional.ofThrowable(() -> Files.readString(Path.of("file.txt")));
content.ifPresentOrElse(
this::processContent,
exception -> log.error("Failed to read file", exception)
);
Comparison with Other Languages' Approaches
It's worth examining how other languages have addressed the error handling problem:
Rust's Result<T, E> and ? operator
Rust's approach using Result<T, E> and the ? operator shows how propagation can be made concise while keeping the type safety benefits. The ? operator automatically unwraps a successful result or returns the error to the caller, making propagation more elegant.
However, Rust's approach requires explicit propagation at each step, which can be more verbose than Java's automatic propagation in certain scenarios.
Kotlin's Approach
Kotlin made all exceptions unchecked but provides functional constructs like runCatching that bring back some type safety in a more modern way:
val result = runCatching {
Files.readString(Path.of("file.txt"))
}
result.fold(
onSuccess = { content -> processContent(content) },
onFailure = { exception -> log.error("Failed to read file", exception) }
)
This approach works well with Kotlin's functional programming paradigm but lacks compile-time enforcement.
Scala's Try[T], Either[A, B], and Effect Systems
Scala offers Try[T], Either[A, B], and various effect systems that encode errors in the type system while integrating well with functional programming:
import scala.util.Try
val fileContent: Try[String] = Try {
Source.fromFile("file.txt").mkString
}
fileContent match {
case Success(content) => processContent(content)
case Failure(exception) => log.error("Failed to read file", exception)
}
This approach preserves type safety while fitting well with Scala's functional paradigm.
Conclusion
Java's checked exceptions were a pioneering attempt to bring type safety to error handling. While the implementation has shortcomings, the core concept aligns with modern type-safe approaches to error handling in languages like Rust and Haskell.
Copying Rust's Result<T, E> might seem like the obvious solution, but it would represent a radical departure from Java's established paradigms. Instead, targeted enhancements to the existing checked exceptions system—like allowing lambdas to declare exceptions and supporting generic exception types—could preserve Java's unique approach while addressing its practical limitations.
The beauty of such improvements is that they'd maintain backward compatibility while making checked exceptions work seamlessly with modern Java features like lambdas and streams. They would acknowledge that the core concept of checked exceptions was sound—the problem was in the implementation details and their interaction with newer language features.
So rather than abandoning checked exceptions entirely, perhaps we should recognize them as a forward-thinking feature that was implemented before its time. As Java continues to evolve, we have an opportunity to refine this system rather than replace it.
In the meantime, next time you're tempted to disparage checked exceptions, remember: they're not just an annoying Java quirk—they're an early attempt at the same type safety paradigm that newer languages now implement with much celebration.
What do you think? Could these improvements make checked exceptions viable for modern Java development? Or is it too late to salvage this controversial feature? I'm interested in hearing your thoughts in the comments.
@hongminhee洪 民憙 (Hong Minhee) Totally agreed. The main problem with the original incarnation of Java's checked exceptions was that it was quite verbose and didn't give an easy way to compress a set of error types (because there is no typedef or such in Java). Zig-style syntax could be easily adapted to checked exceptions for the reasonable convenience.
Emelia 👸🏻 replied to the below article:
Revisiting Java's Checked Exceptions: An Underappreciated Type Safety Feature
洪 民憙 (Hong Minhee) @hongminhee@hackers.pub
Despite their bad reputation in the Java community, checked exceptions provide superior type safety comparable to Rust's Result<T, E> or Haskell's Either a b—we've been dismissing one of Java's best features all along.
Introduction
Few features in Java have been as consistently criticized as checked exceptions. Modern Java libraries and frameworks often go to great lengths to avoid them. Newer JVM languages like Kotlin have abandoned them entirely. Many experienced Java developers consider them a design mistake.
But what if this conventional wisdom is wrong? What if checked exceptions represent one of Java's most forward-thinking features?
In this post, I'll argue that Java's checked exceptions were ahead of their time, offering many of the same type safety benefits that are now celebrated in languages like Rust and Haskell. Rather than abandoning this feature, we should consider how to improve it to work better with modern Java's features.
Understanding Java's Exception Handling Model
To set the stage, let's review how Java's exception system works:
-
Unchecked exceptions (subclasses of
RuntimeExceptionorError): These don't need to be declared or caught. They typically represent programming errors (NullPointerException,IndexOutOfBoundsException) or unrecoverable conditions (OutOfMemoryError). -
Checked exceptions (subclasses of
Exceptionbut notRuntimeException): These must either be caught withtry/catchblocks or declared in the method signature withthrows. They represent recoverable conditions that are outside the normal flow of execution (IOException,SQLException).
Here's how this works in practice:
// Checked exception - compiler forces you to handle or declare it
public void readFile(String path) throws IOException {
Files.readAllLines(Path.of(path));
}
// Unchecked exception - no compiler enforcement
public void processArray(int[] array) {
int value = array[array.length + 1]; // May throw ArrayIndexOutOfBoundsException
}
The Type Safety Argument for Checked Exceptions
At their core, checked exceptions are a way of encoding potential failure modes into the type system via method signatures. This makes certain failure cases part of the API contract, forcing client code to explicitly handle these cases.
Consider this method signature:
public byte[] readFileContents(String filePath) throws IOException
The throws IOException clause tells us something critical: this method might fail in ways related to IO operations. The compiler ensures you can't simply ignore this fact. You must either:
- Handle the exception with a try-catch block
- Propagate it by declaring it in your own method signature
This type-level representation of potential failures aligns perfectly with principles of modern type-safe programming.
Automatic Propagation: A Hidden Advantage
One often overlooked advantage of Java's checked exceptions is their automatic propagation. Once you declare a method as throws IOException, any exception that occurs is automatically propagated to the caller without additional syntax.
Compare this with Rust, where you must use the ? operator every time you call a function that returns a Result:
// Rust requires explicit propagation with ? for each call
fn read_and_process(path: &str) -> Result<(), std::io::Error> {
let content = std::fs::read_to_string(path)?;
process_content(&content)?;
Ok(())
}
// Java automatically propagates exceptions once declared
void readAndProcess(String path) throws IOException {
String content = Files.readString(Path.of(path));
processContent(content); // If this throws IOException, it's automatically propagated
}
In complex methods with many potential failure points, Java's approach leads to cleaner code by eliminating the need for repetitive error propagation markers.
Modern Parallels: Result Types in Rust and Haskell
The approach of encoding failure possibilities in the type system has been adopted by many modern languages, most notably Rust with its Result<T, E> type and Haskell with its Either a b type.
In Rust:
fn read_file_contents(file_path: &str) -> Result<Vec<u8>, std::io::Error> {
std::fs::read(file_path)
}
When calling this function, you can't just ignore the potential for errors—you need to handle both the success case and the error case, often using the ? operator or pattern matching.
In Haskell:
readFileContents :: FilePath -> IO (Either IOException ByteString)
readFileContents path = try $ BS.readFile path
Again, the caller must explicitly deal with both possible outcomes.
This is fundamentally the same insight that motivated Java's checked exceptions: make failure handling explicit in the type system.
Valid Criticisms of Checked Exceptions
If checked exceptions are conceptually similar to these widely-praised error handling mechanisms, why have they fallen out of favor? There are several legitimate criticisms:
1. Excessive Boilerplate in the Call Chain
The most common complaint is the boilerplate required when propagating exceptions up the call stack:
void methodA() throws IOException {
methodB();
}
void methodB() throws IOException {
methodC();
}
void methodC() throws IOException {
// Actual code that might throw IOException
}
Every method in the chain must declare the same exception, creating repetitive code. While automatic propagation works well within a method, the explicit declaration in method signatures creates overhead.
2. Poor Integration with Functional Programming
Java 8 introduced lambdas and streams, but checked exceptions don't play well with them:
// Won't compile because map doesn't expect functions that throw checked exceptions
List<String> fileContents = filePaths.stream()
.map(path -> Files.readString(Path.of(path))) // Throws IOException
.collect(Collectors.toList());
This forces developers to use awkward workarounds:
List<String> fileContents = filePaths.stream()
.map(path -> {
try {
return Files.readString(Path.of(path));
} catch (IOException e) {
throw new UncheckedIOException(e); // Wrap in an unchecked exception
}
})
.collect(Collectors.toList());
3. Interface Evolution Problems
Adding a checked exception to an existing method breaks all implementing classes and calling code. This makes evolving interfaces over time difficult, especially for widely-used libraries and frameworks.
4. Catch-and-Ignore Anti-Pattern
The strictness of checked exceptions can lead to the worst possible outcome—developers simply catching and ignoring exceptions to make the compiler happy:
try {
// Code that might throw
} catch (Exception e) {
// Do nothing or just log
}
This is worse than having no exception checking at all because it provides a false sense of security.
Improving Checked Exceptions Without Abandoning Them
Rather than abandoning checked exceptions entirely, Java could enhance the existing system to address these legitimate concerns. Here are some potential improvements that preserve the type safety benefits while addressing the practical problems:
1. Allow lambdas to declare checked exceptions
One of the biggest pain points with checked exceptions today is their incompatibility with functional interfaces. Consider how much cleaner this would be:
// Current approach - forced to handle or wrap exceptions inline
List<String> contents = filePaths.stream()
.map(path -> {
try {
return Files.readString(Path.of(path));
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
// Potential future approach - lambdas can declare exceptions
List<String> contents = filePaths.stream()
.map((String path) throws IOException -> Files.readString(Path.of(path)))
.collect(Collectors.toList());
This would require updating functional interfaces to support exception declarations:
@FunctionalInterface
public interface Function<T, R, E extends Exception> {
R apply(T t) throws E;
}
2. Generic exception types in throws clauses
Another powerful enhancement would be allowing generic type parameters in throws clauses:
public <E extends Exception> void processWithException(Supplier<Void, E> supplier) throws E {
supplier.get();
}
This would enable much more flexible composition of methods that work with different exception types, bringing some of the flexibility of Rust's Result<T, E> to Java's existing exception system.
3. Better support for exception handling in functional contexts
Unlike Rust which requires the ? operator for error propagation, Java already automatically propagates checked exceptions when declared in the method signature. What Java needs instead is better support for checked exceptions in functional contexts:
// Current approach for handling exceptions in streams
List<String> contents = filePaths.stream()
.map(path -> {
try {
return Files.readString(Path.of(path));
} catch (IOException e) {
throw new RuntimeException(e); // Lose type information
}
})
.collect(Collectors.toList());
// Hypothetical improved API
List<String> contents = filePaths.stream()
.mapThrowing(path -> Files.readString(Path.of(path))) // Preserves checked exception
.onException(IOException.class, e -> logError(e))
.collect(Collectors.toList());
4. Integration with Optional<T> and Stream<T> APIs
The standard library could be enhanced to better support operations that might throw checked exceptions:
// Hypothetical API
Optional<String> content = Optional.ofThrowable(() -> Files.readString(Path.of("file.txt")));
content.ifPresentOrElse(
this::processContent,
exception -> log.error("Failed to read file", exception)
);
Comparison with Other Languages' Approaches
It's worth examining how other languages have addressed the error handling problem:
Rust's Result<T, E> and ? operator
Rust's approach using Result<T, E> and the ? operator shows how propagation can be made concise while keeping the type safety benefits. The ? operator automatically unwraps a successful result or returns the error to the caller, making propagation more elegant.
However, Rust's approach requires explicit propagation at each step, which can be more verbose than Java's automatic propagation in certain scenarios.
Kotlin's Approach
Kotlin made all exceptions unchecked but provides functional constructs like runCatching that bring back some type safety in a more modern way:
val result = runCatching {
Files.readString(Path.of("file.txt"))
}
result.fold(
onSuccess = { content -> processContent(content) },
onFailure = { exception -> log.error("Failed to read file", exception) }
)
This approach works well with Kotlin's functional programming paradigm but lacks compile-time enforcement.
Scala's Try[T], Either[A, B], and Effect Systems
Scala offers Try[T], Either[A, B], and various effect systems that encode errors in the type system while integrating well with functional programming:
import scala.util.Try
val fileContent: Try[String] = Try {
Source.fromFile("file.txt").mkString
}
fileContent match {
case Success(content) => processContent(content)
case Failure(exception) => log.error("Failed to read file", exception)
}
This approach preserves type safety while fitting well with Scala's functional paradigm.
Conclusion
Java's checked exceptions were a pioneering attempt to bring type safety to error handling. While the implementation has shortcomings, the core concept aligns with modern type-safe approaches to error handling in languages like Rust and Haskell.
Copying Rust's Result<T, E> might seem like the obvious solution, but it would represent a radical departure from Java's established paradigms. Instead, targeted enhancements to the existing checked exceptions system—like allowing lambdas to declare exceptions and supporting generic exception types—could preserve Java's unique approach while addressing its practical limitations.
The beauty of such improvements is that they'd maintain backward compatibility while making checked exceptions work seamlessly with modern Java features like lambdas and streams. They would acknowledge that the core concept of checked exceptions was sound—the problem was in the implementation details and their interaction with newer language features.
So rather than abandoning checked exceptions entirely, perhaps we should recognize them as a forward-thinking feature that was implemented before its time. As Java continues to evolve, we have an opportunity to refine this system rather than replace it.
In the meantime, next time you're tempted to disparage checked exceptions, remember: they're not just an annoying Java quirk—they're an early attempt at the same type safety paradigm that newer languages now implement with much celebration.
What do you think? Could these improvements make checked exceptions viable for modern Java development? Or is it too late to salvage this controversial feature? I'm interested in hearing your thoughts in the comments.
@hongminhee洪 民憙 (Hong Minhee) yeah, so I want this in typescript. The lack of types for errors thrown in typescript is it's biggest oversight imo.
@hongminhee洪 民憙 (Hong Minhee) yeah, so I want this in typescript. The lack of types for errors thrown in typescript is it's biggest oversight imo.
@thisismissemEmelia 👸🏻 There's actually an issue in the TypeScript repo proposing exactly this! Many developers agree that typed errors would be a huge improvement. The current “try/catch any” approach feels like a gap in TS's otherwise strong type system.
@curry박준규 확장이 하도 많아서 뭐가 있는지 다 알기가 어려운 것 같아요… 😂
@hongminhee洪 民憙 (Hong Minhee) 이런 표현이 있습니다.
GHC has more flags than the UN.
@hongminhee洪 民憙 (Hong Minhee) 의외로 꽤 예전부터 있던 확장이네요.
@curry박준규 확장이 하도 많아서 뭐가 있는지 다 알기가 어려운 것 같아요… 😂
그 뭐라고 하더라... 대수적 이펙트?
RE: https://hollo.social/@hongminhee/0195b690-cf8e-76b0-84bc-470c9b1c3d5b
타입스크립트에 있었으면 좋겠음
그 뭐라고 하더라... 대수적 이펙트?
RE: https://hollo.social/@hongminhee/0195b690-cf8e-76b0-84bc-470c9b1c3d5b
타입스크립트에 있었으면 좋겠음
@tirr티르 저도 서브타이핑 기반인 TS에 상대적으로 쉽게 도입할 기능이 https://github.com/microsoft/TypeScript/issues/13219 이렇게 오랫동안 진행안되는게 불만입니다. 막상 TS 이펙트 라이브러리들은 |로 흉내내서 잘 쓰고 있더라고요. Haskell처럼 대수적 이펙트는 구현할수있지만 서브타이핑 기반은 아닌 언어에선, 서브타이핑 흉내낸다고 타입레벨 차력쇼하고 있는데 맞는 방향인지 모르겠습니다.
자바의 체크드 예외 재고찰: 저평가된 타입 안전성 기능
------------------------------
## 주요 내용 요약
* 자바의 체크드 예외가 커뮤니티에서 널리 비판받는 기능임에도 타입 안전성 측면에서 뛰어난 장점 보유.
* Rust의 Result<T, E>나 Haskell의 Either a b와 개념적으로 유사한 타입 안전성 메커니즘 제공.
* 체크드 예외가 메서드 시그니처에 잠재적 실패 가능성을 명시적으로 표현하…
------------------------------
https://news.hada.io/topic?id=19877&utm_source=googlechat&utm_medium=bot&utm_campaign=1834
Revisiting Java's Checked Exceptions: An Underappreciated Type Safety Feature
洪 民憙 (Hong Minhee) @hongminhee@hackers.pub
Despite their bad reputation in the Java community, checked exceptions provide superior type safety comparable to Rust's Result<T, E> or Haskell's Either a b—we've been dismissing one of Java's best features all along.
Introduction
Few features in Java have been as consistently criticized as checked exceptions. Modern Java libraries and frameworks often go to great lengths to avoid them. Newer JVM languages like Kotlin have abandoned them entirely. Many experienced Java developers consider them a design mistake.
But what if this conventional wisdom is wrong? What if checked exceptions represent one of Java's most forward-thinking features?
In this post, I'll argue that Java's checked exceptions were ahead of their time, offering many of the same type safety benefits that are now celebrated in languages like Rust and Haskell. Rather than abandoning this feature, we should consider how to improve it to work better with modern Java's features.
Understanding Java's Exception Handling Model
To set the stage, let's review how Java's exception system works:
-
Unchecked exceptions (subclasses of
RuntimeExceptionorError): These don't need to be declared or caught. They typically represent programming errors (NullPointerException,IndexOutOfBoundsException) or unrecoverable conditions (OutOfMemoryError). -
Checked exceptions (subclasses of
Exceptionbut notRuntimeException): These must either be caught withtry/catchblocks or declared in the method signature withthrows. They represent recoverable conditions that are outside the normal flow of execution (IOException,SQLException).
Here's how this works in practice:
// Checked exception - compiler forces you to handle or declare it
public void readFile(String path) throws IOException {
Files.readAllLines(Path.of(path));
}
// Unchecked exception - no compiler enforcement
public void processArray(int[] array) {
int value = array[array.length + 1]; // May throw ArrayIndexOutOfBoundsException
}
The Type Safety Argument for Checked Exceptions
At their core, checked exceptions are a way of encoding potential failure modes into the type system via method signatures. This makes certain failure cases part of the API contract, forcing client code to explicitly handle these cases.
Consider this method signature:
public byte[] readFileContents(String filePath) throws IOException
The throws IOException clause tells us something critical: this method might fail in ways related to IO operations. The compiler ensures you can't simply ignore this fact. You must either:
- Handle the exception with a try-catch block
- Propagate it by declaring it in your own method signature
This type-level representation of potential failures aligns perfectly with principles of modern type-safe programming.
Automatic Propagation: A Hidden Advantage
One often overlooked advantage of Java's checked exceptions is their automatic propagation. Once you declare a method as throws IOException, any exception that occurs is automatically propagated to the caller without additional syntax.
Compare this with Rust, where you must use the ? operator every time you call a function that returns a Result:
// Rust requires explicit propagation with ? for each call
fn read_and_process(path: &str) -> Result<(), std::io::Error> {
let content = std::fs::read_to_string(path)?;
process_content(&content)?;
Ok(())
}
// Java automatically propagates exceptions once declared
void readAndProcess(String path) throws IOException {
String content = Files.readString(Path.of(path));
processContent(content); // If this throws IOException, it's automatically propagated
}
In complex methods with many potential failure points, Java's approach leads to cleaner code by eliminating the need for repetitive error propagation markers.
Modern Parallels: Result Types in Rust and Haskell
The approach of encoding failure possibilities in the type system has been adopted by many modern languages, most notably Rust with its Result<T, E> type and Haskell with its Either a b type.
In Rust:
fn read_file_contents(file_path: &str) -> Result<Vec<u8>, std::io::Error> {
std::fs::read(file_path)
}
When calling this function, you can't just ignore the potential for errors—you need to handle both the success case and the error case, often using the ? operator or pattern matching.
In Haskell:
readFileContents :: FilePath -> IO (Either IOException ByteString)
readFileContents path = try $ BS.readFile path
Again, the caller must explicitly deal with both possible outcomes.
This is fundamentally the same insight that motivated Java's checked exceptions: make failure handling explicit in the type system.
Valid Criticisms of Checked Exceptions
If checked exceptions are conceptually similar to these widely-praised error handling mechanisms, why have they fallen out of favor? There are several legitimate criticisms:
1. Excessive Boilerplate in the Call Chain
The most common complaint is the boilerplate required when propagating exceptions up the call stack:
void methodA() throws IOException {
methodB();
}
void methodB() throws IOException {
methodC();
}
void methodC() throws IOException {
// Actual code that might throw IOException
}
Every method in the chain must declare the same exception, creating repetitive code. While automatic propagation works well within a method, the explicit declaration in method signatures creates overhead.
2. Poor Integration with Functional Programming
Java 8 introduced lambdas and streams, but checked exceptions don't play well with them:
// Won't compile because map doesn't expect functions that throw checked exceptions
List<String> fileContents = filePaths.stream()
.map(path -> Files.readString(Path.of(path))) // Throws IOException
.collect(Collectors.toList());
This forces developers to use awkward workarounds:
List<String> fileContents = filePaths.stream()
.map(path -> {
try {
return Files.readString(Path.of(path));
} catch (IOException e) {
throw new UncheckedIOException(e); // Wrap in an unchecked exception
}
})
.collect(Collectors.toList());
3. Interface Evolution Problems
Adding a checked exception to an existing method breaks all implementing classes and calling code. This makes evolving interfaces over time difficult, especially for widely-used libraries and frameworks.
4. Catch-and-Ignore Anti-Pattern
The strictness of checked exceptions can lead to the worst possible outcome—developers simply catching and ignoring exceptions to make the compiler happy:
try {
// Code that might throw
} catch (Exception e) {
// Do nothing or just log
}
This is worse than having no exception checking at all because it provides a false sense of security.
Improving Checked Exceptions Without Abandoning Them
Rather than abandoning checked exceptions entirely, Java could enhance the existing system to address these legitimate concerns. Here are some potential improvements that preserve the type safety benefits while addressing the practical problems:
1. Allow lambdas to declare checked exceptions
One of the biggest pain points with checked exceptions today is their incompatibility with functional interfaces. Consider how much cleaner this would be:
// Current approach - forced to handle or wrap exceptions inline
List<String> contents = filePaths.stream()
.map(path -> {
try {
return Files.readString(Path.of(path));
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
// Potential future approach - lambdas can declare exceptions
List<String> contents = filePaths.stream()
.map((String path) throws IOException -> Files.readString(Path.of(path)))
.collect(Collectors.toList());
This would require updating functional interfaces to support exception declarations:
@FunctionalInterface
public interface Function<T, R, E extends Exception> {
R apply(T t) throws E;
}
2. Generic exception types in throws clauses
Another powerful enhancement would be allowing generic type parameters in throws clauses:
public <E extends Exception> void processWithException(Supplier<Void, E> supplier) throws E {
supplier.get();
}
This would enable much more flexible composition of methods that work with different exception types, bringing some of the flexibility of Rust's Result<T, E> to Java's existing exception system.
3. Better support for exception handling in functional contexts
Unlike Rust which requires the ? operator for error propagation, Java already automatically propagates checked exceptions when declared in the method signature. What Java needs instead is better support for checked exceptions in functional contexts:
// Current approach for handling exceptions in streams
List<String> contents = filePaths.stream()
.map(path -> {
try {
return Files.readString(Path.of(path));
} catch (IOException e) {
throw new RuntimeException(e); // Lose type information
}
})
.collect(Collectors.toList());
// Hypothetical improved API
List<String> contents = filePaths.stream()
.mapThrowing(path -> Files.readString(Path.of(path))) // Preserves checked exception
.onException(IOException.class, e -> logError(e))
.collect(Collectors.toList());
4. Integration with Optional<T> and Stream<T> APIs
The standard library could be enhanced to better support operations that might throw checked exceptions:
// Hypothetical API
Optional<String> content = Optional.ofThrowable(() -> Files.readString(Path.of("file.txt")));
content.ifPresentOrElse(
this::processContent,
exception -> log.error("Failed to read file", exception)
);
Comparison with Other Languages' Approaches
It's worth examining how other languages have addressed the error handling problem:
Rust's Result<T, E> and ? operator
Rust's approach using Result<T, E> and the ? operator shows how propagation can be made concise while keeping the type safety benefits. The ? operator automatically unwraps a successful result or returns the error to the caller, making propagation more elegant.
However, Rust's approach requires explicit propagation at each step, which can be more verbose than Java's automatic propagation in certain scenarios.
Kotlin's Approach
Kotlin made all exceptions unchecked but provides functional constructs like runCatching that bring back some type safety in a more modern way:
val result = runCatching {
Files.readString(Path.of("file.txt"))
}
result.fold(
onSuccess = { content -> processContent(content) },
onFailure = { exception -> log.error("Failed to read file", exception) }
)
This approach works well with Kotlin's functional programming paradigm but lacks compile-time enforcement.
Scala's Try[T], Either[A, B], and Effect Systems
Scala offers Try[T], Either[A, B], and various effect systems that encode errors in the type system while integrating well with functional programming:
import scala.util.Try
val fileContent: Try[String] = Try {
Source.fromFile("file.txt").mkString
}
fileContent match {
case Success(content) => processContent(content)
case Failure(exception) => log.error("Failed to read file", exception)
}
This approach preserves type safety while fitting well with Scala's functional paradigm.
Conclusion
Java's checked exceptions were a pioneering attempt to bring type safety to error handling. While the implementation has shortcomings, the core concept aligns with modern type-safe approaches to error handling in languages like Rust and Haskell.
Copying Rust's Result<T, E> might seem like the obvious solution, but it would represent a radical departure from Java's established paradigms. Instead, targeted enhancements to the existing checked exceptions system—like allowing lambdas to declare exceptions and supporting generic exception types—could preserve Java's unique approach while addressing its practical limitations.
The beauty of such improvements is that they'd maintain backward compatibility while making checked exceptions work seamlessly with modern Java features like lambdas and streams. They would acknowledge that the core concept of checked exceptions was sound—the problem was in the implementation details and their interaction with newer language features.
So rather than abandoning checked exceptions entirely, perhaps we should recognize them as a forward-thinking feature that was implemented before its time. As Java continues to evolve, we have an opportunity to refine this system rather than replace it.
In the meantime, next time you're tempted to disparage checked exceptions, remember: they're not just an annoying Java quirk—they're an early attempt at the same type safety paradigm that newer languages now implement with much celebration.
What do you think? Could these improvements make checked exceptions viable for modern Java development? Or is it too late to salvage this controversial feature? I'm interested in hearing your thoughts in the comments.
uv2nix는 uv보다 구리고, cabal2nix는 cabal보다 구린데, Nix는 uv + cabal + ... 보다 낫다. Nix 커뮤니티를 키우려면, 후자를 이해시키고(쉬움) 전자에 대해 익스큐즈하도록 설득해야한다(어려움) .
@hongminhee洪 民憙 (Hong Minhee) 저는 Nix를 쓰고 있습니다. 한동안 Haskell 안쓰고있다가 오랜만에 돌아왔더니 다들 Nix 쓰고있어서 그냥 따라 쓰는 상태입니다. Nix가 해결하려는 문제와 방향은 공감하지만, Haksell + Nix가 막 엄청 좋은지는 잘 모르겠는 상태에요.
Nix로 그냥 GHC랑 Cabal, Stack 버전만 잡고 나머지는 Cabal, Stack 등의 기존 하스켈 툴링에 맡기는 방법이 있고, 또 Nix가 패키지 다운받아서 빌드하는 역할까지 대신해버리는 방법이 있는데, 제가 쓰고 있는 방법은 후자입니다.
@bglbgl gwyng 아… Nix가 Hackage에 올라온 패키지에 1:1 대응되는 패키지가 항상 있기 때문에 가능한 방법이겠네요. 참고하겠습니다. 알려주셔서 감사합니다!
Hot take: Despite their bad reputation in the Java community, checked exceptions provide superior type safety comparable to Rust's Result<T, E> or Haskell's Either a b—we've been dismissing one of Java's best features all along.
Lobsters 스타일의 초대 시스템을 먼저 구현할까, 아니면 인용을 먼저 구현할까? 🤔
@parksbSimon Park 어디는 망치가 너무 많아서 문제고 어디는 너무 적어서 문제죠ㅋㅋ
@bglbgl gwyng 갑자기 딴 이야기긴 한데,
@bglbgl gwyng 님은 Haskell 툴체인으로 Stack 쓰시나요, 아니면 Cabal 쓰시나요? 몇 년 전부터 Stack을 썼는데 요즘엔 Stack 쪽이 개발이 잘 안 되는 것 같아서 다시 Cabal로 넘어가야 하나 고민하고 있거든요…
@hongminhee洪 民憙 (Hong Minhee) 제가 링크를 넣은 찬진님의 글에 이미 주제가 포함되어 있습니다. 해커즈펍에서는 글 마지막에
#스레드 개선
이렇게 붙어서 나오네요.
@arkjunJuntai Park 아… 그럼 Threads의 주제는 ActivityPub 상에서 마크업이 되진 않는 것 같네요. Threads 내부에서만 활용 가능한 기능인가 봅니다. 참고로 이찬진 님의 해당 글은 Activity Streams 객체로 아래와 같이 표현되고 있습니다:
{
"id": "https://threads.net/ap/users/17841400639660143/post/17976118301827877/",
"type": "Note",
"content": "<p>이제 스레드의 '주제(Topic)' 기능을 '해시태그'라고 부를 수 없겠네요. <br /><br />게시물 작성 화면에서 '해시태그'를 의미하는 '#' 버튼이 없어졌고 대신 '주제 추가' 버튼이 들어갔습니다. <br /><br />물론 전에 '해시태그'와 비슷하게 보일 때에도<br /><br />- 게시물에 하나 밖에 쓸 수 없었고<br />- 태그에 스페이스를 쓸 수 있었고<br />- '#'이 없었으니<br /><br />정확하게 해시태그는 아니었지만 이제는 입력하는 방법도 그렇고 표시되는 위치도 그렇고 확실히 '주제' 기능이라고 불러야겠네요.<br />#스레드 개선</p>",
"published": "2025-03-20T18:35:52-07:00",
"contentMap": {
"ko": "<p>이제 스레드의 '주제(Topic)' 기능을 '해시태그'라고 부를 수 없겠네요. <br /><br />게시물 작성 화면에서 '해시태그'를 의미하는 '#' 버튼이 없어졌고 대신 '주제 추가' 버튼이 들어갔습니다. <br /><br />물론 전에 '해시태그'와 비슷하게 보일 때에도<br /><br />- 게시물에 하나 밖에 쓸 수 없었고<br />- 태그에 스페이스를 쓸 수 있었고<br />- '#'이 없었으니<br /><br />정확하게 해시태그는 아니었지만 이제는 입력하는 방법도 그렇고 표시되는 위치도 그렇고 확실히 '주제' 기능이라고 불러야겠네요.<br />#스레드 개선</p>"
},
"attributedTo": "https://threads.net/ap/users/17841400639660143/",
"url": "https://www.threads.net/@chanjin65/post/DHcXMcczYx6",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://threads.net/ap/users/17841400639660143/followers/"
],
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"tag": [],
"attachment": [
{
"type": "Image",
"url": "https://scontent-ssn1-1.cdninstagram.com/v/t51.75761-15/485651795_17904023592105580_5390283833466719793_n.jpg?stp=dst-jpg_e35_tt6&_nc_cat=104&ccb=1-7&_nc_sid=18de74&_nc_ohc=Ds02qFOIJUUQ7kNvgGjxYCA&_nc_oc=AdnVGfWmGdOHV2sZAvWtcv1M0iaLjV4A-RbC0lDI_hgvwYrnt69HjBGo_js8NkvX6T0&_nc_zt=23&_nc_ht=scontent-ssn1-1.cdninstagram.com&edm=AMJMky4EAAAA&_nc_gid=dudtlAu0RjYNhaifxVdB_Q&oh=00_AYG5YaNk9YbLpECGixfB74Lgi9-VSZhhYWByBvTi6lGBaw&oe=67E29C5A",
"name": "Photo by 이차지 on March 20, 2025. 사람 1명, 화면 및 문구: '10:26 취소 새로운 스레드 베타 베타·페디버스에공유합니다. 페디버스메 공유합니 chanjin65 chanjin650>7 이>주제추7 스레드에추가 누구에게나답글및인용허용 인용허용 N ㅂ 2 天 世 3 C 4 ٦ 5 人 6 Η ㅔ ㅁ니ㅇ리ㅎ 2 ᄒ @ ١ ᄏ ㅏ E 天 ㅍ T AAA 123 간격 AH KIy'의 Twitter 스크린샷일 수 있음.",
"width": 1125,
"height": 2436
}
]
}
해커스펍에 제안 PR 올렸다 후후
스레드에 주제 (Topic) 기능이 추가되었습니다. 다만 연합우주(페디버스)에서는 해시태그로 보이는 듯 합니다.
https://hackers.pub/@chanjin65@threads.net/0195b665-a214-7309-9fc6-850bf13588db
이제 스레드의 '주제(Topic)' 기능을 '해시태그'라고 부를 수 없겠네요.<게시물 작성 화면에서 '해시태그'를 의미하는 '#' 버튼이 없어졌고 대신 '주제 추가' 버튼이 들어갔습니다.<물론 전에 '해시태그'와 비슷하게 보일 때에도- 게시물에 하나 밖에 쓸 수 없었고- 태그에 스페이스를 쓸 수 있었고- '#'이 없었으니정확하게 해시태그는 아니었지만 이제는 입력하는 방법도 그렇고 표시되는 위치도 그렇고 확실히 '주제' 기능이라고 불러야겠네요.#스레드 개선
이제 스레드의 '주제(Topic)' 기능을 '해시태그'라고 부를 수 없겠네요.<게시물 작성 화면에서 '해시태그'를 의미하는 '#' 버튼이 없어졌고 대신 '주제 추가' 버튼이 들어갔습니다.<물론 전에 '해시태그'와 비슷하게 보일 때에도- 게시물에 하나 밖에 쓸 수 없었고- 태그에 스페이스를 쓸 수 있었고- '#'이 없었으니정확하게 해시태그는 아니었지만 이제는 입력하는 방법도 그렇고 표시되는 위치도 그렇고 확실히 '주제' 기능이라고 불러야겠네요.#스레드 개선
hackers.pub
Link author:
이찬진@chanjin65@threads.net
@arkjunJuntai Park ActivityPub으로는 여전히 Hashtag로 될 것 같긴 한데 실제로 주제를 건 게시물의 예시를 봐야 알 수 있겠네요… 혹시 주제를 건 Threads 게시물이 보이면 제보 부탁드립니다. ㅎㅎㅎ
@hongminhee洪 民憙 (Hong Minhee)
@parksbSimon Park
개인적으로 Node 프로젝트 때 써본 마지막의 경험으론 패키지 매니저는 pnpm 이 체감상 제일 빠른 느낌이었는데 요즘은 어떤지 궁금하네요.
다들 썼으면 하는 말씀을 해주시니 Deno 도 도전해보고 싶군요.
@arkjunJuntai Park
@parksbSimon Park 요즘도 pnpm이 가장 빠르긴 한 것 같아요. 아, Bun이 더 빠른가? 잘 모르겠군요…
이제 단문에 포함된 대표 링크에 대해 Open Graph 메타데이터를 추출하여 표시하게 되었습니다. 또한, 링크에 fediverse:creator 메타데이터가 있을 경우, 저자의 연합우주 프로필까지 하단에 표시하게 됩니다.
이제 단문에 포함된 대표 링크에 대해 Open Graph 메타데이터를 추출하여 표시하게 되었습니다. 또한, 링크에 fediverse:creator 메타데이터가 있을 경우, 저자의 연합우주 프로필까지 하단에 표시하게 됩니다.
- 처음 써 보는 조용한 공개. 조용한 공개는 일반 공개와 뭐가 다를까. 연합 우주에는 올라가지 않는 거려나?
- 블스 연동을 했는데 hackers.pub 프로필에 쓴 텍스트가 블스 프로필로는 다 옮겨지지 않는다. 글자 수 제한이 있는 걸까요.
- 앱 지면을 무슨 아마존처럼 탐험하며 여기도 광고 넣을 수 있겠다! 저기도! 하는 흐름에 현기증이 난다. 이게 우리 회사 임원인지 사모펀드인지⋯.
- 마라톤 행사만 끝나고 나면 이것저것 해 보고 싶은 게 많은데.
@linear “조용한 공개”는 “공개”와 거의 모든 면에서 같지만 로그인 안 했을 때 Hackers' Pub 첫 화면에서 보이지 않는다는 점, 외부 연합우주 서버에서 “연합 타임라인”에 뜨지 않는다는 점만 달라요.
모든 자바스크립트 개발자들이 단 하나의 패키지 매니저와 단 하나의 빌드 시스템, 단 하나의 모듈 시스템을 사용하면 좋겠다고 진심으로 생각한다
@parksbSimon Park 사견으로는 그냥 다들 Deno를 썼으면 좋겠네요…
모든 자바스크립트 개발자들이 단 하나의 패키지 매니저와 단 하나의 빌드 시스템, 단 하나의 모듈 시스템을 사용하면 좋겠다고 진심으로 생각한다
C++ 표준화 위원회(WG21)에게 C++의 원 저자인 비야네 스트롭스트룹Bjarne Stroustrup이 보낸 메일이 이번 달 초에 본인에 의해 공개된 모양이다. C++가 요즘 안전하지 않은 언어라고 열심히 얻어 맞고 있는 게 싫은지 프로파일(P3081)이라고 하는 언어 부분집합을 정의하려고 했는데, 프로파일이 다루는 문제들이 아주 쉬운 것부터 연구가 필요한 것까지 한데 뒤섞여 있어 구현이 매우 까다롭기에 해당 제안이 적절하지 않음을 올해 초에 가멸차게 까는 글(P3586)이 올라 오자 거기에 대한 응답으로 작성된 것으로 보인다. 더 레지스터의 표현을 빌면 "(본지가 아는 한) 스트롭스트룹이 이 정도로 강조해서 말하는 건 2018년 이래 처음"이라나.
여론은 당연히 호의적이지 않은데, 기술적인 반론이 대부분인 P3586과는 달리 해당 메일은 원래 공개 목적이 아니었음을 감안해도 기술적인 얘기는 쏙 빼 놓고 프로파일이 "코드를 안 고치고도 안전성을 가져 갈 수 있다"는 허황된 주장에 기반해 그러니까 프로파일을 당장 집어 넣어야 한다고 주장하고 있으니 그럴 만도 하다. 스트롭스트룹이 그렇게 이름을 언급하지 않으려고 했던 러스트를 굳이 들지 않아도, 애당초 (이 또한 계속 부정하고 싶겠지만) C++의 주요 장점 중 하나였던 강력한 C 호환성이 곧 메모리 안전성의 가장 큰 적이기 때문에 프로파일이 아니라 프로파일 할아버지가 와도 안전성을 진짜로 확보하려면 코드 수정이 필수적이고, 프로파일이 그 문제를 해결한다고 주장하는 건 눈 가리고 아웅이라는 것을 이제는 충분히 많은 사람들이 깨닫지 않았는가. 스트롭스트룹이 허황된 주장을 계속 반복하는 한 C++는 안전해질 기회가 없을 듯 하다.
@nyeongAn Nyeong (安寧) 이슈 설명할 힘도 없을때는 ai한테 '${이슈 내용} ㄱㄱ' 라고만 쳐도 열심히 잘해주는데 그럴땐 미안한 맘이 듭니다
@bglbgl gwyng
@nyeongAn Nyeong (安寧) 저는 나중에 AI에게 지배 받을지도 몰라서 미리미리 존댓말을 쓰고 있습니다.
Mastodon이나 Misskey 등에서 민감한 내용으로 지정한 단문의 내용이나 첨부 이미지는 이제 Hackers' Pub에서 뿌옇게 보이게 됩니다. 마우스 커서를 가져다 대면 또렷하게 보이게 됩니다. 다만, Hackers' Pub에서 쓰는 단문을 민감한 내용으로 지정하는 기능은 없습니다. (아마도 앞으로도 없을 것 같습니다.)
참고로 Hackers' Pub은 대부분의 경우 Fedify의 bleeding edge 버전을 쓰고 있습니다.
오늘 하려던 Fedify 작업은 얼추 끝내서, 다시 Hackers' Pub 작업에 손을 댑니다.
Got an interesting question today about #Fedify's outgoing #queue design!
Some users noticed we create separate queue messages for each recipient inbox rather than queuing a single message and handling the splitting later. There's a good reason for this approach.
In the #fediverse, server response times vary dramatically—some respond quickly, others slowly, and some might be temporarily down. If we processed deliveries in a single task, the entire batch would be held up by the slowest server in the group.
By creating individual queue items for each recipient:
- Fast servers get messages delivered promptly
- Slow servers don't delay delivery to others
- Failed deliveries can be retried independently
- Your UI remains responsive while deliveries happen in the background
It's a classic trade-off: we generate more queue messages, but gain better resilience and user experience in return.
This is particularly important in federated networks where server behavior is unpredictable and outside our control. We'd rather optimize for making sure your posts reach their destinations as quickly as possible!
What other aspects of Fedify's design would you like to hear about? Let us know!
Coming soon in #Fedify 1.5.0: Smart fan-out for efficient activity delivery!
After getting feedback about our queue design, we're excited to introduce a significant improvement for accounts with large follower counts.
As we discussed in our previous post, Fedify currently creates separate queue messages for each recipient. While this approach offers excellent reliability and individual retry capabilities, it causes performance issues when sending activities to thousands of followers.
Our solution? A new two-stage “fan-out” approach:
- When you call
Context.sendActivity(), we'll now enqueue just one consolidated message containing your activity payload and recipient list - A background worker then processes this message and re-enqueues individual delivery tasks
The benefits are substantial:
Context.sendActivity()returns almost instantly, even for massive follower counts- Memory usage is dramatically reduced by avoiding payload duplication
- UI responsiveness improves since web requests complete quickly
- The same reliability for individual deliveries is maintained
For developers with specific needs, we're adding a fanout option with three settings:
"auto"(default): Uses fanout for large recipient lists, direct delivery for small ones"skip": Bypasses fanout when you need different payload per recipient"force": Always uses fanout even with few recipients
// Example with custom fanout setting
await ctx.sendActivity(
{ identifier: "alice" },
recipients,
activity,
{ fanout: "skip" } // Directly enqueues individual messages
);
This change represents months of performance testing and should make Fedify work beautifully even for extremely popular accounts!
For more details, check out our docs.
What other #performance optimizations would you like to see in future Fedify releases?
Getting back to #Fedify development today! Working on optimizing the outgoing activity queue to improve response times. Currently focusing on reducing latency when sending posts to large follower counts—should make the whole publishing experience feel much snappier.
Finished the #Fedify queue optimization work. Tests written, docs completed. Should make activity delivery much faster for high-follower accounts.
저는 AI에게 감사 인사를 하는 데에도 돈이 든다는 걸 깨달아버려서, 이제는 감사도 표하지 않는 삭막한 인간이 되고야 말았습니다.
…라고 썼지만, Fedify의 신기능을 테스트하기 위해 Hackers' Pub에 적용해 보고 있습니다. ㅎㅎㅎ
참고로 Hackers' Pub은 대부분의 경우 Fedify의 bleeding edge 버전을 쓰고 있습니다.
@hongminhee洪 民憙 (Hong Minhee) 해커스펍도 페디파이로 만드신 거죠? 홀로도요?
@curry박준규 네, 맞습니다!
오늘 하루는 Fedify 작업을 해야 해서 아마도 Hackers' Pub 개발은 못 할 것 같습니다.
…라고 썼지만, Fedify의 신기능을 테스트하기 위해 Hackers' Pub에 적용해 보고 있습니다. ㅎㅎㅎ
이제 여기서 글 쓰면 블스에도 보이는 건가?
와 보인다! 보여요!












