如何在 ELM 中重构此功能?

How do I refactor this function in ELM?

我正在尝试学习函数式编程,并决定从 Project Euler 的问题 1 开始:基本上将所有小于 1000 且能被 3 或 5 整除的数字相加 (link: a link)。

这是我写的代码。它输出 3 或 5 的因子列表(仍然需要弄清楚如何求和)。

import Html exposing (text)
import Array

main =
  text (
toString
[findSum_maxZ 3 5 1000]
  )

findSum_maxZ x y max_z =
  Array.filter isDivisible_x_or_y (Array.initialize max_z identity)

isDivisible_x_or_y x = 
  if x % 3 == 0 || x % 5 == 0 then True else False

我的问题是我引用了 3 和 5 两次,但我无法使用更抽象的 'x' 和'y' 的附加参数调用 isDivisible。我的目标是确定删除这些人为可变值的有效方法,以便最终用户只需修改每个输入值一次。有什么建议吗?

如果这个问题很愚蠢,我深表歉意,没有太多关于 ELM 的可用信息(尤其是与我使用过的 python、c、c++、java 等相比)和我仍然对函数式编程术语不太满意。感谢您提供任何帮助。

ML 语言的妙处在于,您几乎可以自由地构建自己的 "dialect" 来解决问题。

您可以使用柯里化将 xy 参数应用到您的函数,创建一个已设置提供值的新函数。

import Html exposing (text)
import Array

main = [findSum 3 5 1000]
           |>toString
           |>text

findSum x y maxZ =
      let
         isDivisibleByX = isDivisible x
         isDivisibleByY = isDivisible y
      in
         Array.initialize maxZ identity
         |>Array.filter isDivisibleByX
         |>Array.filter isDivisibleByY
         --as you can see, it is possible to use a list instead of creating
         --new functions, it is up to you to check which abstraction works
         --the best


isDivisible a b =
      b % a == 0 

您也可以使用单个函数,而无需求助于柯里化:

import Html exposing (text)
import Array

main = [findSum 3 5 1000]
       |>toString
       |>text

findSum x y maxZ =
     Array.initialize maxZ identity
     |>Array.filter (\n-> isDivisible x n ) --or just (isDivisible x)
     |>Array.filter (\n-> isDivisible y n)


isDivisible a b =
  b % a == 0 

如果你只想用一行过滤数组,你可以这样做:

import Html exposing (text)

main = findSum 3 5 1000
       |>toString
       |>text

findSum x y maxZ =
     let
        divisibles = \n-> isDivisible x n && isDivisible y n
     in
       List.range 0 maxZ 
       |>List.filter divisibles

isDivisible a b =
  b % a == 0 

您问题的最直接答案是,您可以让 isDivisible_x_or_y 取两个因数,然后使用柯里化将部分应用的函数传递给 Array.filter

也就是说,你可以这样定义isDivisible_x_or_y(我也删除了if True then True else False语法,直接return表达式):

isDivisible_x_or_y x y val =
    val % x == 0 || val % y == 0

柯里化是一种只向函数提供部分参数,并取回采用其余参数的函数的能力。所以,isDivisible_x_or_y 的类型定义是 Int -> Int -> Int -> Bool(也就是说,它接受三个 Int 值和 return 一个 Bool)。如果我们为 xy 参数提供值(例如 isDivisible_x_y 3 5),我们现在得到一个类型定义为 Int -> Bool 的函数。这是 Array.filter.

期望的类型

您可以在 https://ellie-app.com/sdxWFL9ynka1

查看一个工作示例

另外几个注意事项:

List 在 Elm 中比 Array 更常见。如果您需要获取特定索引处的项目,您只会使用 Array 。您可以使用 List.range

而不是 Array.initialize

使用管道运算符 |> 通常可以使您的代码更易于阅读。而不是 text (toString (getValue)),你有 getValue |> toString |> text,它现在按操作发生的顺序排列,并且没有额外的括号。整个程序可以是一个简单的管道(不过在很多情况下,将所有内容都放入一个管道中可能会过多):

main =
    List.range 0 max_z
        |> List.filter (isDivisible_x_or_y 3 5)
        |> toString
        |> text

isDivisible_x_or_y x y val =
    val % x == 0 || val % y == 0