从Java和Haskell一些代码对比中认识函数式编程 - morgenthum.dev

19-09-20 banq
              

很多朋友和同事问我为什么谈论Haskell。在我学习Haskell之前,我总是使用Java,C和C ++等主流语言 - 并且仍然喜欢它们。那么一个命令式的开发人员怎么会变成Haskell粉丝?在本文中,我想解释一下 - 特别是对于函数编程经验较少的开发人员。

控制流

控制流程描述了如何告诉程序要做什么 - 制定算法。有三个基本控制元素:

  • 序列 - 按顺序执行代码
  • 重复 - 重复执行代码
  • 条件 - 按条件将代码拆分为分支

面向对象编程:

  • 序列是使用逐行逐句的语句实现
  • 重复是使用循环实现,如for或while语句或递归
  • 条件是使用if ... else或switch陈述

这是一个使用Java的简单示例,它以文本为中心。文本作为字符串数组传递。每一行都是该数组的一个元素:

void alignCenter(String[] text)
{
    int maxLength = 0;
    for (String line : text) {
        if (line.length() > maxLength) {
            maxLength = line.length();
        }
    }

    for (int i = 0; i < text.length; ++i)
    {
        int spaceCount = (maxLength - text[i].length()) / 2;

        StringBuilder builder = new StringBuilder();
        for (int j = 0; j < spaceCount; ++j)
        {
            builder.append(' ');
        }
        builder.append(text[i]);

        text[i] = builder.toString();
    }
}

函数式编程

  • 序列是使用一链串的调用实现chained calls
  • 重复是使用递归实现
  • 条件是使用模式匹配实现,如case ... of或if ... else表达式

下面是Haskell中的相同示例,它显示了模式匹配和递归的用法:

alignCenter :: [String] -> [String]
alignCenter xs = alignCenter' maxLength xs
    where maxLength = maximum (map length xs)

alignCenter' :: Int -> [String] -> [String]
alignCenter' _ [] = []
alignCenter' n (x:xs) = (replicate spaceCount ' ' ++ x) : alignCenter' n xs
    where spaceCount = div (n - length x) 2

下面是一个简短的版本,通过使用map和lambda函数避免递归:

alignCenter :: [String] -> [String]
alignCenter xs = map (\x -> replicate (div (n - length x) 2) ' ' ++ x) xs
    where n = maximum (map length xs)

函数的第一行是函数签名:

alignCenter :: [String] -> [String]

这告诉我们,我们有一个名为alignCenter的函数,它将一个字符串列表作为输入,并返回一个新的字符串列表作为输出(从左到右理解)。

然后第二行代码中是函数体的内容,第一个函数确定字符串列表中的最长行并调用第二个函数。我们通过一个简单的表达式maximum (map length xs)终止了我们的oop代码的第一个循环。

那么它是怎样工作的?我们来看看所有相关maximum (map length xs)函数的签名:

length :: [a] -> Int
map :: (a -> b) -> [a] -> [b]
maximum :: [a] -> a

length函数获取任何类型的列表并返回一个Int。所有小写类型的类型签名是类型变量,类似于在List<T>Java中的类型T。

map函数需要获取两个输入参数:第一个是类型a -> b;第二个获取[a]并返回

那么它是什么意思“它需要一个函数作为参数”?对,是真的!您可以将函数作为参数传递,既不像C中那样的函数指针也不像Java中那样引用方法引用 - 实际函数作为第一类值。

将函数作为参数使用或将新函数作为结果返回的函数称为高阶函数。那么这个功能有什么作用呢?它将传递[a]中每个元素给a -> b函数,在这个函数中,将a转为b,并且收集collect形成一个新的list

现在让我们解决类型变量map length xs,其中xs的类型为[String]:

map :: (String -> Int) -> [String] -> [Int]

您需要知道这String是一个类型的同义词[Char],表示字符列表。这就是为什么它与length函数兼容的原因。表达式map length ["Hello", "World!"]将解析为[5, 6]。(计算这两个字符串的长度),我们感兴趣的是列表里面最长的字符串的长度,所以我们传递给结果列表maximum返回列表,这是最大的数字6。

更多介绍点击标题见原文。