发布于 2026-01-06 0 阅读
0

Clojure悖论

Clojure悖论

几年前,我偶然读到了保罗·格雷厄姆的《Python悖论》。在书中,格雷厄姆认为,Lisp和Python程序员往往是真正热爱编程的人,因此,他们的编程水平往往比Java程序员更高。这种观点从根本上改变了我对Python这门语言的看法。

起初,我发现 Python 缺少分号且依赖缩进,这让我感到奇怪和不适应。我甚至一度认为 Python 只能用来构建一些简单的应用程序。然而,随着无服务器计算、机器学习和数据科学的兴起,Python 的强大功能日益凸显。这门语言运行速度越来越快,其生态系统也在迅速发展壮大。像 FastAPI 和 Pandas 这样的库尤其出色,让我们能够简洁高效地解决问题。

As programmers, our job is to solve problems, and since we read more code than we write, having fewer lines of code reduces the surface area for bugs to hide and helps us avoid cognitive overload.

当我开始使用 AWS 的 Boto3 时,我意识到以前需要 30 行 Java 代码才能完成的任务,现在只需 3 行 Python 代码就能搞定。这简直令人难以置信。别误会,Java 仍然是我最喜欢的编程语言之一,而且随着它不断更新迭代,它也变得越来越好。但是,用 Java 完成一些基本任务所需的繁琐步骤,有时是我们都希望避免的。

最近我一直在尝试使用 Go 语言。虽然它以简洁著称,但恕我直言,我觉得它的代码过于冗长。我知道很多优秀的工具都是用 Go 开发的,而且有些想法和应用也确实应该用 Go 来开发。Go的编译速度快、内存使用效率高,使其成为一个强有力的竞争者,它甚至可能是开发者体验和性能的最佳结合点,而这在现代云原生应用中变得越来越重要。

Clojure 1

然而,在行业摸爬滚打了十年,用多种语言部署过应用之后,我仍然是 Clojure 的忠实拥趸。尽管 Clojure 相对小众,但它融合了其他语言的理念,例如 Go 语言的 goroutine。它是一种 Lisp 方言,本质上是不可变的,并且从设计之初就考虑到了并发性。最让我印象深刻的是,Clojure 让你能够专注于解决问题本身,而无需被不必要的繁琐流程所束缚。大部分代码都围绕着问题本身;它是面向数据的,而且我发现它常常能帮助我进入一种心流状态(快乐状态),在这种状态下,编程变得真正令人愉悦。

目前我对 Go 的感觉比较复杂。虽然它在并发性和简洁性方面有很多优点,但我发现它的代码库往往更大,也更繁琐。相比之下,Clojure 生成的代码更稳定,主要由纯函数构成。

永恒的理念

我一直很喜欢那些永恒的理念,因为它们往往是最重要、最基础的,但它们也最容易被忽视,我觉得 Clojure 完全拥抱了所有这些理念。

  • 主要由纯函数组成的程序更加健壮,也更容易测试。
  • 不变性降低了复杂性。
  • 较小的程序错误较少。
  • 软件开发从根本上来说就是组合。
  • 我们应该尽量减少
  • 偶然的复杂性会使系统更难理解、维护和扩展。

代码示例

在这个例子中,我从在线文本文件中读取一本书,并进行基本处理,以说明如何在不同的编程语言和范式中解决同一个问题。

我的观点:缺少集合以及诸如 filter、map 和 reduce 之类的内置函数,使得问题更难解决。诸如过滤之类的基本任务通常需要以命令式的方式完成。

package main

import (
    "fmt"
    "io"
    "net/http"
    "regexp"
    "sort"
    "strings"
)

var commonWords = map[string]struct{}{
    "a": {}, "able": {}, "about": {}, "across": {}, "after": {}, "all": {}, "almost": {}, "also": {}, "am": {}, "among": {},
    "an": {}, "and": {}, "any": {}, "are": {}, "as": {}, "at": {}, "be": {}, "because": {}, "been": {}, "but": {}, "by": {},
    "can": {}, "cannot": {}, "could": {}, "dear": {}, "did": {}, "do": {}, "does": {}, "either": {}, "else": {}, "ever": {},
    "every": {}, "for": {}, "from": {}, "get": {}, "got": {}, "had": {}, "has": {}, "have": {}, "he": {}, "her": {}, "hers": {},
    "him": {}, "his": {}, "how": {}, "however": {}, "i": {}, "if": {}, "in": {}, "into": {}, "is": {}, "it": {}, "its": {},
    "just": {}, "least": {}, "let": {}, "like": {}, "likely": {}, "may": {}, "me": {}, "might": {}, "most": {}, "must": {},
    "my": {}, "neither": {}, "no": {}, "nor": {}, "not": {}, "of": {}, "off": {}, "often": {}, "on": {}, "only": {}, "or": {},
    "other": {}, "our": {}, "own": {}, "rather": {}, "said": {}, "says": {}, "she": {}, "should": {}, "since": {}, "so": {},
    "some": {}, "than": {}, "that": {}, "the": {}, "their": {}, "them": {}, "then": {}, "there": {}, "these": {}, "they": {},
    "this": {}, "those": {}, "through": {}, "to": {}, "too": {}, "more": {}, "upon": {}, "us": {}, "wants": {}, "was": {},
    "we": {}, "were": {}, "what": {}, "when": {}, "where": {}, "which": {}, "while": {}, "who": {}, "whom": {}, "why": {},
    "will": {}, "with": {}, "would": {}, "yet": {}, "you": {}, "your": {}, "shall": {}, "before": {}, "now": {}, "one": {},
    "even": {},
}

func getBook() string {
    resp, err := http.Get("https://www.gutenberg.org/cache/epub/84/pg84.txt")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
    return string(body)
}

func getWords(book string) []string {
    re := regexp.MustCompile(`[\w’]+`)
    return re.FindAllString(book, -1)
}

func filterWords(words []string) []string {
    var result []string
    for _, word := range words {
        w := strings.ToLower(word)
        _, ok := commonWords[w]
        if !ok {
            result = append(result, w)
        }
    }
    return result
}

func getFrequentWords(words []string, n int) map[string]int {
    var filteredWords []string
    for _, word := range words {
        _, ok := commonWords[word]
        if !ok {
            filteredWords = append(filteredWords, strings.ToLower(word))
        }
    }
    var unorderedWords = make(map[string]int)
    for _, word := range words {
        unorderedWords[word]++
    }
    type wordFrequency struct {
        word  string
        count int
    }
    var wordFrequencies []wordFrequency
    for word, count := range unorderedWords {
        wordFrequencies = append(wordFrequencies, wordFrequency{word, count})
    }
    sort.Slice(wordFrequencies, func(i, j int) bool {
        return wordFrequencies[i].count > wordFrequencies[j].count
    })

    topN := make(map[string]int)
    for i := 0; i < len(wordFrequencies) && i < n; i++ {
        topN[wordFrequencies[i].word] = wordFrequencies[i].count
    }

    return topN
}

func main() {
    book := getBook()
    words := getWords(book)
    filteredWords := filterWords(words)
    fmt.Println("Total words:", len(words))
    fmt.Println("Frequent words:", getFrequentWords(filteredWords, 10))

}

Enter fullscreen mode Exit fullscreen mode

Java

我的看法:它能完成任务。自 Java 8 以来,这门语言一直在进步。尽管它有些冗长,但你会发现现在我们有了收集器和函数来轻松完成任务。繁琐之处在于,为了解决一个问题,不得不把所有东西都放到类里。

package jorgetovar.book;

import org.springframework.web.client.RestTemplate;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class Book {

    private static final Set<String> commonWords = Set.of(
            "a", "able", "about", "across", "after", "all", "almost", "also", "am", "among", "an",
            "and", "any", "are", "as", "at", "be", "because", "been", "but", "by", "can", "cannot",
            "could", "dear", "did", "do", "does", "either", "else", "ever", "every", "for", "from",
            "get", "got", "had", "has", "have", "he", "her", "hers", "him", "his", "how", "however",
            "i", "if", "in", "into", "is", "it", "its", "just", "least", "let", "like", "likely",
            "may", "me", "might", "most", "must", "my", "neither", "no", "nor", "not", "of", "off",
            "often", "on", "only", "or", "other", "our", "own", "rather", "said", "says", "she",
            "should", "since", "so", "some", "than", "that", "the", "their", "them", "then",
            "there", "these", "they", "this", "those", "through", "to", "too", "more", "upon",
            "us", "wants", "was", "we", "were", "what", "when", "where", "which", "while", "who",
            "whom", "why", "will", "with", "would", "yet", "you", "your", "shall", "before", "now", "one",
            "even"
    );

    public static String getBook() {
        RestTemplate restTemplate = new RestTemplate();
        String bookUrl = "https://www.gutenberg.org/cache/epub/84/pg84.txt";
        return restTemplate.getForObject(bookUrl, String.class);
    }

    public static List<String> getWords(String book) {
        List<String> words = new ArrayList<>();
        Pattern wordPattern = Pattern.compile("[\\w’]+");
        Matcher matcher = wordPattern.matcher(book);
        while (matcher.find()) {
            words.add(matcher.group());
        }
        return words;
    }

    public static List<Map.Entry<String, Long>> getFrequentWords(List<String> words, int takeN) {
        return words.stream()
                .map(String::toLowerCase)
                .filter(word -> !commonWords.contains(word))
                .collect(Collectors.groupingBy(word -> word, Collectors.counting()))
                .entrySet()
                .stream()
                .sorted((e1, e2) -> Long.compare(e2.getValue(), e1.getValue()))
                .limit(takeN)
                .map(e -> Map.entry(e.getKey(), e.getValue()))
                .toList();
    }

    public static Map<Integer, List<String>> getLongestWords(List<String> words, int takeN) {
        return words.stream()
                .map(String::toLowerCase)
                .distinct()
                .sorted(getLongestWord())
                .limit(takeN)
                .collect(Collectors.groupingBy(String::length));
    }

    public static boolean isPalindrome(String word) {
        return word.contentEquals(new StringBuilder(word).reverse());
    }

    public static List<String> getLongestPalindromes(List<String> words, int takeN) {
        return words.stream()
                .map(String::toLowerCase)
                .filter(word -> !commonWords.contains(word))
                .distinct()
                .filter(Book::isPalindrome)
                .sorted(getLongestWord())
                .limit(takeN)
                .toList();
    }

    private static Comparator<String> getLongestWord() {
        return Comparator.comparingInt(String::length).reversed();
    }

    public static void main(String[] args) {
        String book = getBook();
        List<String> words = getWords(book);
        System.out.println("Total Words: " + words.size());
        System.out.println("Most Frequent Words:");
        getFrequentWords(words, 10).forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));

        System.out.println("\nLongest Words Grouped by Length:");
        getLongestWords(words, 10).forEach((length, group) -> System.out.println("Length " + length + ": " + group));

        System.out.println("\nLongest Palindromes:");
        getLongestPalindromes(words, 3).forEach(System.out::println);
    }


}

Enter fullscreen mode Exit fullscreen mode

Kotlin

我的看法:对我而言,这可能是最有趣、最强大的企业级语言。它对函数和不可变性都有很好的支持。

package jorgetovar.book

import org.springframework.web.client.RestTemplate


val commonWords = setOf(
    "a", "able", "about", "across", "after", "all", "almost", "also", "am", "among", "an",
    "and", "any", "are", "as", "at", "be", "because", "been", "but", "by", "can", "cannot",
    "could", "dear", "did", "do", "does", "either", "else", "ever", "every", "for", "from",
    "get", "got", "had", "has", "have", "he", "her", "hers", "him", "his", "how", "however",
    "i", "if", "in", "into", "is", "it", "its", "just", "least", "let", "like", "likely",
    "may", "me", "might", "most", "must", "my", "neither", "no", "nor", "not", "of", "off",
    "often", "on", "only", "or", "other", "our", "own", "rather", "said", "says", "she",
    "should", "since", "so", "some", "than", "that", "the", "their", "them", "then",
    "there", "these", "they", "this", "those", "through", "to", "too", "more", "upon",
    "us", "wants", "was", "we", "were", "what", "when", "where", "which", "while", "who",
    "whom", "why", "will", "with", "would", "yet", "you", "your", "shall", "before", "now", "one",
    "even"
)

fun getBook(): String {
    val restTemplate = RestTemplate()
    val bookUrl = "https://www.gutenberg.org/cache/epub/84/pg84.txt"
    return restTemplate.getForObject(bookUrl, String::class.java) ?: ""
}

fun getWords(book: String): List<String> {
    return "[\\w’]+".toRegex().findAll(book).map { it.value }.toList()
}

fun getFrequentWords(words: List<String>, takeN: Int): List<Pair<String, Int>> {
    val filteredWords = words
        .map { it.lowercase() }
        .filter { it !in commonWords }

    return filteredWords
        .groupingBy { it }
        .eachCount()
        .toList()
        .sortedByDescending { it.second }
        .take(takeN)

}

fun getLongestWords(words: List<String>, takeN: Int): Map<Int, List<String>> {
    val uniqueWords = words
        .map { it.lowercase() }
        .distinct()
    return uniqueWords
        .sortedByDescending { it.length }
        .take(takeN)
        .groupBy { it.length }
}

fun isPalindrome(word: String): Boolean {
    return word == word.reversed()
}

fun getLongestPalindromes(words: List<String>, takeN: Int): List<String> {
    val uniqueWords = words
        .map { it.lowercase() }
        .filter { it !in commonWords }
        .distinct()
    val palindromes = uniqueWords
        .filter { isPalindrome(it) }
    return palindromes
        .sortedByDescending { it.length }.take(takeN)
}


fun main() {
    val book = getBook()
    val words = getWords(book)
    println("Total Words: ${words.size}")
    println("Most Frequent Words:")
    println(getFrequentWords(words, 10))
    println("Longest Words Grouped by Length:")
    println(getLongestWords(words, 5))
    println("Longest Palindromes:")
    println(getLongestPalindromes(words, 3))
}

Enter fullscreen mode Exit fullscreen mode

Python

我的看法:我真的很喜欢用这门语言。有时候它会变得很混乱,因为它过于宽松,允许你修改变量等等。但总的来说,你会发现列表推导式非常适合解决这类问题。我不喜欢使用类的结果,但在这个例子中,它已经足够用了。

import requests
import re

from collections import Counter, defaultdict


def get_book():
    book = requests.get("https://www.gutenberg.org/cache/epub/84/pg84.txt")
    return book.text


def get_words(book):
    return re.findall(r"[a-zA-Z0-9’]+", book)


common_words = {
    "a", "able", "about", "across", "after", "all", "almost", "also", "am", "among", "an",
    "and", "any", "are", "as", "at", "be", "because", "been", "but", "by", "can", "cannot",
    "could", "dear", "did", "do", "does", "either", "else", "ever", "every", "for", "from",
    "get", "got", "had", "has", "have", "he", "her", "hers", "him", "his", "how", "however",
    "i", "if", "in", "into", "is", "it", "its", "just", "least", "let", "like", "likely",
    "may", "me", "might", "most", "must", "my", "neither", "no", "nor", "not", "of", "off",
    "often", "on", "only", "or", "other", "our", "own", "rather", "said", "says", "she",
    "should", "since", "so", "some", "than", "that", "the", "their", "them", "then",
    "there", "these", "they", "this", "those", "through", "to", "too", "more", "upon",
    "us", "wants", "was", "we", "were", "what", "when", "where", "which", "while", "who",
    "whom", "why", "will", "with", "would", "yet", "you", "your", "shall", "before", "now", "one",
    "even"
}


def get_frequent_words(words, take_n):
    frequent_words = [word.lower() for word in words if word.lower() not in common_words]
    word_frequencies = Counter(frequent_words)
    return word_frequencies.most_common(take_n)


def get_longest_words(words, take_n):
    unique_words = set(word.lower() for word in words)
    longest_groups = defaultdict(list)
    sorted_works = sorted(unique_words, key=len, reverse=True)[:take_n]
    for word in sorted_works:
        longest_groups[len(word)].append(word)
    return dict(longest_groups)


def is_palindrome(word):
    return word == word[::-1]


def get_longest_palindromes(words, take_n):
    unique_words = set(word.lower() for word in words if word.lower() not in common_words)
    palindromes = [word for word in unique_words if is_palindrome(word)]
    palindromes.sort(key=len, reverse=True)
    return palindromes[:take_n]


def main():
    book = get_book()
    words = get_words(book)
    print("Total words:", len(words))
    print(get_frequent_words(words, 10))
    print(get_longest_words(words, 10))
    print(get_longest_palindromes(words, 3))


if __name__ == "__main__":
    main()

Enter fullscreen mode Exit fullscreen mode

Clojure

我的观点:Clojure 的问题在于它的小众性。通常来说,理解这门语言的基础知识和理念比较困难。大量的括号让很多人感到不适应,但总的来说,我认为它是最美的语言实现。


(ns clojure-book.core
  [:require [clojure.string :as str]]
  (:gen-class))

(def book (slurp "https://www.gutenberg.org/cache/epub/84/pg84.txt"))

(def words (re-seq #"[\w|’]+" book))

(def common-words
  #{"a" "able" "about" "across" "after" "all" "almost" "also" "am" "among" "an"
    "and" "any" "are" "as" "at" "be" "because" "been" "but" "by" "can" "cannot"
    "could" "dear" "did" "do" "does" "either" "else" "ever" "every" "for" "from"
    "get" "got" "had" "has" "have" "he" "her" "hers" "him" "his" "how" "however"
    "i" "if" "in" "into" "is" "it" "its" "just" "least" "let" "like" "likely"
    "may" "me" "might" "most" "must" "my" "neither" "no" "nor" "not" "of" "off"
    "often" "on" "only" "or" "other" "our" "own" "rather" "said" "says" "she"
    "should" "since" "so" "some" "than" "that" "the" "their" "them" "then"
    "there" "these" "they" "this" "those" "through" "to" "too" "more" "upon"
    "us" "wants" "was" "we" "were" "what" "when" "where" "which" "while" "who"
    "whom" "why" "will" "with" "would" "yet" "you" "your" "shall" "before" "now" "one"
    "even"
    })

(defn palindrome? [word]
  (= (seq word) (reverse (seq word)))
  )

(defn frequent-words [take-n]
  (->> words
       (map str/lower-case)
       (remove common-words)
       (frequencies)
       (sort-by val)
       (take-last take-n))
  )

(defn longest-words [take-n]
  (->> words
       (map str/lower-case)
       (distinct)
       (sort-by count)
       (take-last take-n)
       (group-by count)
       )
  )

(defn longest-palindromes [take-n]
  (->> words
       (map str/lower-case)
       (distinct)
       (filter palindrome?)
       (sort-by count)
       (take-last take-n)
       )
  )

(defn -main
  [& args]
  (println (str "Total words:" (count words)))
  (println (take 10 words))
  (println (frequent-words 10))
  (println (longest-words 10))
  (println (longest-palindromes 3))

  )

Enter fullscreen mode Exit fullscreen mode

结论

软件不断发展演进,客户对他们使用和开发的程序的期望也越来越高。然而,我们应该始终专注于解决问题、消除不必要的复杂性,并以我们的专业技能为荣。没有“最佳”编程语言,只有能够帮助我们解决特定问题的工具。即使是处理遗留系统,我们也可以通过良好的命名规范、最佳实践、改进架构以及整体优化项目状态,从而产生积极的影响。

对于工程师而言,现在是通过软件为社会创造价值的最佳时机。

如果您喜欢这些文章,请访问我的博客jorgetovar.dev

文章来源:https://dev.to/jorgetovar/the-clojure-paradox-41ke