如何在TypeScript/JavaScript中强制确保纯函数仅调用纯函数?
Great question! You're absolutely right that comments and naming conventions are just soft hints—they don't prevent mistakes. What you need is compile-time or static analysis enforcement that blocks pure functions from calling non-pure ones. Here are concrete, language-specific solutions that do exactly that:
1. Haskell: Type System Enforces Purity Natively
Haskell is a pure functional language by design, so its type system inherently separates pure logic from side effects. Non-pure functions (those with side effects like I/O) are marked with IO (or other effect monads) in their type signature. Pure functions can't call these non-pure functions directly—doing so would cause a type mismatch that the compiler rejects immediately.
Example:
-- Pure function: no effect type in signature pureAdd :: Int -> Int -> Int pureAdd a b = a + b -- Non-pure function: explicitly marked with IO impurePrint :: String -> IO () impurePrint s = putStrLn s -- ❌ COMPILE ERROR: Can't use IO in a pure function -- badPureFunc :: Int -> Int -- badPureFunc x = pureAdd x (impurePrint "hi" >> return 5)
The compiler will throw an error here because impurePrint returns an IO value, which can't be used in a function that expects a plain Int. This is a hard guarantee, not a hint.
2. Scala: Annotations and Effect Types
Scala 3 introduces built-in support for marking pure functions with the pure modifier, which restricts the function to only call other pure functions. Additionally, you can use effect types (like IO from libraries like Cats Effect) to isolate side effects, ensuring pure logic never touches non-pure code.
Example with Scala 3's pure modifier:
import scala.annotation.pure @pure def pureAdd(a: Int, b: Int): Int = a + b def impureLog(s: String): Unit = println(s) // ❌ COMPILE ERROR: @pure function can't call non-pure code // @pure // def badFunc(x: Int): Int = { // impureLog("Calling badFunc") // pureAdd(x, 1) // }
The @pure annotation tells the compiler to validate that the function doesn't invoke any side-effecting operations, including calls to non-pure functions.
3. Rust: Nightly #[pure] Annotation
Rust's nightly compiler includes an unstable #[pure] feature that enforces a function's purity. When you mark a function with this attribute, the compiler checks that:
- The function doesn't modify any state (no mutable references, no global variables)
- It only calls other
#[pure]functions - It doesn't perform any unsafe operations or side effects
Example:
#![feature(pure)] #[pure] fn pure_add(a: i32, b: i32) -> i32 { a + b } fn impure_print(s: &str) { println!("{}", s); } // ❌ COMPILE ERROR: #[pure] function can't call non-pure code // #[pure] // fn bad_func(x: i32) -> i32 { // impure_print("Hello"); // pure_add(x, 1) // }
While this is a nightly feature, it provides strict compile-time enforcement once enabled.
4. Static Analysis Tools for Multi-Paradigm Languages
If your language doesn't have native purity enforcement (like Java, TypeScript, or Kotlin), you can use static analysis tools with custom rules:
- Java/Kotlin: Use SpotBugs or Detekt with rules that check functions marked
@Pure(fromorg.jetbrains.annotations.Pure) don't call non-pure functions. - TypeScript: Use ESLint with plugins like
eslint-plugin-functional, which includes rules to enforce purity and restrict function calls within pure functions.
Key Takeaway
The most reliable way to enforce this is to use language-native type systems or compile-time annotations—these turn your purity requirements into hard rules that the compiler enforces. Pure functional languages like Haskell offer the strongest out-of-the-box guarantees, while multi-paradigm languages can leverage annotations or static analysis to achieve the same result.
内容的提问来源于stack exchange,提问作者Tom Hale

