函数类型

当你浏览像 elm/coreelm/html 这样的包时,你一定看到带有多个箭头的函数。例如

String.repeat : Int -> String -> String
String.join : String -> List String -> String

为什么有如此之多的箭头?这里究竟发生了什么?

隐藏的括号

当你看到所有括号时,一切将开始变得清晰。例如,编写 String.repeat 的类型如下所示

String.repeat : Int -> (String -> String)

这是一个函数,它接收一个 Int,然后生成另一个函数。我们来看看它在中实际应用

[ { "input": "String.repeat", "value": "\u001b[36m<function>\u001b[0m", "type_": "Int -> String -> String" }, { "input": "String.repeat 4", "value": "\u001b[36m<function>\u001b[0m", "type_": "String -> String" }, { "input": "String.repeat 4 \"ha\"", "value": "\u001b[93m\"hahahaha\"\u001b[0m", "type_": "String" }, { "input": "String.join", "value": "\u001b[36m<function>\u001b[0m", "type_": "String -> List String -> String" }, { "input": "String.join \"|\"", "value": "\u001b[36m<function>\u001b[0m", "type_": "List String -> String" }, { "input": "String.join \"|\" [\"red\",\"yellow\",\"green\"]", "value": "\u001b[93m\"red|yellow|green\"\u001b[0m", "type_": "String" } ]

因此,从概念上讲,每个函数都接受一个参数。它可以返回另一个接受一个参数的函数。依此类推。在某些时候,它将停止返回函数。

我们可以始终使用括号来表明这就是实际发生的情况,但是当你有多个参数时,这将变得相当笨重。这与编写 4 * 2 + 5 * 3 而不是 (4 * 2) + (5 * 3) 背后的逻辑相同。这意味着需要额外学习一些东西,但它非常常见,因此是值得的。

好的,但这个特性在第一位有什么意义?为什么不执行 (Int, String) -> String 并同时给出所有参数呢?

部分应用

在 Elm 程序中使用 List.map 函数相当常见

List.map : (a -> b) -> List a -> List b

需要两个论点:一个函数和一个列表。从那里它转变列表中的每一个元素用那个函数。下面是一些例子

  • List.map String.reverse ["part","are"] == ["trap","era"]
  • List.map String.length ["part","are"] == [4,3]

现在记住String.repeat 4它的类型是String -> String吗?这意味着我们可以说

  • List.map (String.repeat 2) ["ha","choo"] == ["haha","choochoo"]

表达式(String.repeat 2)是一个String -> String函数,所以我们可以直接使用它。无需说(\str -> String.repeat 2 str).

Elm 也使用约定,即数据结构始终是最后一个论点在整个生态系统中。这意味着通常在设计函数时考虑这种可能的用法,使这成为一种相当常见的技术。

现在重要的是要记住这可能会被过度使用!它有时候很方便和明确,但我发现最好适当地使用它。所以我总是建议在事情变得有点复杂,甚至一点点复杂时,分解顶层的辅助函数。这样它就有了一个明确的名称,论点被命名,并且很容易测试这个新的辅助函数。在我们的例子中,这意味着创建

-- List.map reduplicate ["ha","choo"]

reduplicate : String -> String
reduplicate string =
  String.repeat 2 string

这种情况真的很简单,但是(1)现在更清楚的是我感兴趣于被称为倍增的语言现象,并且(2)随着我的程序的发展,很容易为reduplicate添加新的逻辑。也许我想要shm-reduplication支持?

换句话说,如果您的部分应用程序变长,请使其成为一个辅助函数。并且如果它是多行的,那么它肯定应该被转换为一个顶级辅助函数!此建议也适用于使用匿名函数。

注意:如果你在使用此建议时最终产生了“太多”函数,我建议使用诸如-- REDUPLICATION之类的注释来概述接下来的 5 个或 10 个函数。老派!我在以前的示例中已经用-- UPDATE-- VIEW注释来展示了这一点,但这是我在所有代码中使用的通用技术。如果您担心使用此建议使文件变得太长,我建议观看文件的生命

管道

Elm 还有一个管道操作符,它依赖于部分应用程序。例如,假设我们有一个sanitize函数,用于将用户输入转换为整数

-- BEFORE
sanitize : String -> Maybe Int
sanitize input =
  String.toInt (String.trim input)

我们可以这样重写它

-- AFTER
sanitize : String -> Maybe Int
sanitize input =
  input
    |> String.trim
    |> String.toInt

因此在这个“管道”中,我们将输入传递给String.trim,然后将其传递给String.toInt.

这很简洁,因为它允许采用许多人喜欢的“从左到右”的阅读方式,但 **管道很容易被过度使用!** 如果有三个或四个步骤,那么分离出顶级辅助函数后,代码通常会变得更清晰。现在,变换有了名称。参数已被命名。它有一个类型注释。这种方式更加具有自描述性,而且你的团队成员和未来的你会感谢它!这样,测试逻辑也会变得更容易。

注意:我个人更喜欢 BEFORE,但也许只是因为我是在没有管道的情况下学习函数式编程语言的。

匹配的 个结果。

    没有与 匹配的结果。