从golang走向Haskell - 从Go的func到Haskell的纯函数

  • 时间:2025-11-22 23:01 作者: 来源: 阅读:0
  • 扫一扫,手机访问
摘要:在Go中,函数是工具;在Haskell中,函数是艺术。让我们看看这种转变是如何发生的。核心概念:纯函数 vs 命令式函数咱们Go程序员写函数是这样的:func add(a int, b int) int { return a + b } 但是Haskell里写函数是这样的:add :: Int -> Int -> Int add a b = a + b 等等,这里没有func关

在Go中,函数是工具;在Haskell中,函数是艺术。让我们看看这种转变是如何发生的。

核心概念:纯函数 vs 命令式函数

咱们Go程序员写函数是这样的:

func add(a int, b int) int {
    return a + b
}

但是Haskell里写函数是这样的:

add :: Int -> Int -> Int
add a b = a + b

等等,这里没有func关键字!没有return关键字!没有花括号!这是什么鬼?

对比分析:两种函数思维

Go中的函数:命令式的思维

先看看咱们熟悉的Go函数是怎么写的:

package main

import "fmt"

// 基本函数
func add(a int, b int) int {
    return a + b
}

// 多返回值
func divide(a int, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// 可变参数
func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

func main() {
    result := add(5, 3)
    fmt.Printf("5 + 3 = %d
", result)
    
    quotient, err := divide(10, 2)
    if err != nil {
        fmt.Printf("Error: %v
", err)
    } else {
        fmt.Printf("10 / 2 = %d
", quotient)
    }
    
    total := sum(1, 2, 3, 4, 5)
    fmt.Printf("Sum of 1,2,3,4,5 = %d
", total)
}

Haskell中的函数:声明式的思维

再看看Haskell是怎么搞的:

-- 基本函数
add :: Int -> Int -> Int
add a b = a + b

-- 处理除零错误(使用Maybe类型)
divide :: Int -> Int -> Maybe Int
divide a 0 = Nothing
divide a b = Just (a `div` b)

-- 列表求和
sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs

main :: IO ()
main = do
    let result = add 5 3
    putStrLn $ "5 + 3 = " ++ show result
    
    let quotient = divide 10 2
    case quotient of
        Nothing -> putStrLn "Error: division by zero"
        Just q -> putStrLn $ "10 / 2 = " ++ show q
    
    let total = sumList [1, 2, 3, 4, 5]
    putStrLn $ "Sum of [1,2,3,4,5] = " ++ show total

应用实例:函数式编程实践

咱们来写两个文件,看看这两种函数写法到底有什么不同:

Go版本 (functions.go)

package main

import "fmt"

// 计算阶乘
func factorial(n int) int {
    if n <= 1 {
        return 1
    }
    return n * factorial(n-1)
}

// 判断是否为偶数
func isEven(n int) bool {
    return n%2 == 0
}

// 过滤偶数
func filterEven(numbers []int) []int {
    var result []int
    for _, num := range numbers {
        if isEven(num) {
            result = append(result, num)
        }
    }
    return result
}

func main() {
    // 测试阶乘
    fmt.Printf("Factorial of 5: %d
", factorial(5))
    
    // 测试偶数判断
    fmt.Printf("Is 4 even? %t
", isEven(4))
    fmt.Printf("Is 5 even? %t
", isEven(5))
    
    // 测试过滤
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    evens := filterEven(numbers)
    fmt.Printf("Even numbers: %v
", evens)
}

Haskell版本 (functions.hs)

-- 计算阶乘
factorial :: Int -> Int
factorial 0 = 1
factorial 1 = 1
factorial n = n * factorial (n - 1)

-- 判断是否为偶数
isEven :: Int -> Bool
isEven n = n `mod` 2 == 0

-- 过滤偶数
filterEven :: [Int] -> [Int]
filterEven [] = []
filterEven (x:xs)
    | isEven x = x : filterEven xs
    | otherwise = filterEven xs

main :: IO ()
main = do
    -- 测试阶乘
    putStrLn $ "Factorial of 5: " ++ show (factorial 5)
    
    -- 测试偶数判断
    putStrLn $ "Is 4 even? " ++ show (isEven 4)
    putStrLn $ "Is 5 even? " ++ show (isEven 5)
    
    -- 测试过滤
    let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    let evens = filterEven numbers
    putStrLn $ "Even numbers: " ++ show evens

深入理解:函数式编程的精髓

1. 函数签名的理解

Go中的函数签名:

func add(a int, b int) int

Haskell中的函数签名:

add :: Int -> Int -> Int

从程序员的角度对比理解:

  • Go:func关键字 + 参数列表 + 返回类型
  • Haskell:函数名 + :: + 类型签名

Haskell的类型签名Int -> Int -> Int表明:接受一个Int,返回一个接受Int并返回Int的函数。这叫做柯里化(Currying)。

2. 纯函数的概念

Haskell中的函数是纯函数,这意味着:

  • 一样的输入总是产生一样的输出
  • 没有副作用(不修改外部状态)
  • 不依赖外部状态

对比Go:

// Go中可能有副作用
var globalCounter int = 0

func increment() int {
    globalCounter++  // 副作用:修改全局变量
    return globalCounter
}
-- Haskell中纯函数
increment :: Int -> Int
increment x = x + 1  -- 没有副作用,总是返回x+1

3. 模式匹配的威力

Haskell中的函数定义可以使用模式匹配:

-- 阶乘函数使用模式匹配
factorial :: Int -> Int
factorial 0 = 1        -- 模式1:输入是0
factorial 1 = 1        -- 模式2:输入是1
factorial n = n * factorial (n - 1)  -- 模式3:其他情况

这比Go中的if-else更清晰:

func factorial(n int) int {
    if n == 0 {
        return 1
    } else if n == 1 {
        return 1
    } else {
        return n * factorial(n-1)
    }
}

4. 柯里化的理解

Haskell中的函数都是柯里化的:

-- 这个函数
add :: Int -> Int -> Int
add a b = a + b

-- 等价于
add :: Int -> (Int -> Int)
add a =  -> a + b

-- 可以部分应用
add5 :: Int -> Int
add5 = add 5

-- 使用
result = add5 3  -- 结果是8

Go中需要显式创建部分应用的函数:

func add(a, b int) int {
    return a + b
}

func add5(b int) int {
    return add(5, b)
}

实际应用:数学计算器

让我们创建一个更复杂的数学计算器来展示函数式编程的威力:

Go版本 (math_calculator.go)

package main

import (
    "fmt"
    "math"
)

// 计算平方
func square(x float64) float64 {
    return x * x
}

// 计算距离
func distance(x1, y1, x2, y2 float64) float64 {
    dx := x2 - x1
    dy := y2 - y1
    return math.Sqrt(dx*dx + dy*dy)
}

// 判断点是否在圆内
func isInCircle(centerX, centerY, radius, x, y float64) bool {
    dist := distance(centerX, centerY, x, y)
    return dist <= radius
}

func main() {
    // 测试平方函数
    fmt.Printf("Square of 5: %.2f
", square(5))
    
    // 测试距离函数
    dist := distance(0, 0, 3, 4)
    fmt.Printf("Distance from (0,0) to (3,4): %.2f
", dist)
    
    // 测试圆内判断
    inCircle := isInCircle(0, 0, 5, 3, 4)
    fmt.Printf("Point (3,4) in circle (0,0,5): %t
", inCircle)
}

Haskell版本 (math_calculator.hs)

-- 计算平方
square :: Double -> Double
square x = x * x

-- 计算距离
distance :: Double -> Double -> Double -> Double -> Double
distance x1 y1 x2 y2 = sqrt (dx * dx + dy * dy)
    where
        dx = x2 - x1
        dy = y2 - y1

-- 判断点是否在圆内
isInCircle :: Double -> Double -> Double -> Double -> Double -> Bool
isInCircle centerX centerY radius x y = dist <= radius
    where
        dist = distance centerX centerY x y

main :: IO ()
main = do
    -- 测试平方函数
    putStrLn $ "Square of 5: " ++ show (square 5)
    
    -- 测试距离函数
    let dist = distance 0 0 3 4
    putStrLn $ "Distance from (0,0) to (3,4): " ++ show dist
    
    -- 测试圆内判断
    let inCircle = isInCircle 0 0 5 3 4
    putStrLn $ "Point (3,4) in circle (0,0,5): " ++ show inCircle

常见陷阱和注意事项

1. 函数应用语法

-- 正确:函数应用不需要括号
add 5 3

-- 错误:不要用括号
add(5, 3)  -- 这会报错

2. 类型声明的重大性

-- 推荐:显式声明类型
factorial :: Int -> Int
factorial n = if n <= 1 then 1 else n * factorial (n - 1)

-- 不推荐:省略类型声明
factorial n = if n <= 1 then 1 else n * factorial (n - 1)

3. 模式匹配的顺序

-- 错误:更具体的模式应该放在前面
factorial :: Int -> Int
factorial n = n * factorial (n - 1)  -- 这会无限递归
factorial 0 = 1

-- 正确:具体模式在前
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)

展望

从Go的func到Haskell的纯函数,我们看到了编程思维的转变。虽然语法看起来有点不同,但纯函数和模式匹配的概念已经开始展现函数式编程的魅力。

接下来,我们将深入Haskell的类型系统,那里有更多令人惊叹的设计等待着我们。

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】八股已死、场景当立(场景篇-设计模式篇)(2025-11-22 23:27)
【系统环境|】群、环、域(2025-11-22 23:26)
【系统环境|】深度解析:基于Python的分布式缓存系统实现与性能优化(2025-11-22 23:26)
【系统环境|】TP区块链下载全解析:从技术原理到代码实现(2025-11-22 23:25)
【系统环境|】大模型在急性肾衰竭预测及临床方案制定中的应用研究(2025-11-22 23:25)
【系统环境|】特价股票投资中的可持续供应链管理整合方法(2025-11-22 23:24)
【系统环境|】第193期 如何微调大语言模型(LLM)(内含源码细节)(2025-11-22 23:23)
【系统环境|】用Python构建智能推荐系统:技术赋能美好生活(2025-11-22 23:23)
【系统环境|】企业估值中的氢能源应用评估(2025-11-22 23:22)
【系统环境|】ansible 学习之路(2025-11-22 23:22)
手机二维码手机访问领取大礼包
返回顶部