关于学习“如何思考功能性”的建议?

发布于 2024-08-07 13:38:52 字数 495 浏览 4 评论 0原文

作为函数式语言的新手(几周前我开始接触 Erlang——我能接触到的第一种函数式语言)。

我开始编写一些小算法(例如 left_rotate_listbubble_sort、 merge_sort 等)。我发现自己经常迷失在诸如“我应该使用辅助列表来存储中间结果吗?”之类的决策中。以及“我应该创建一个辅助函数来执行此操作吗?”

一段时间后,我发现函数式编程(如果我所说的根本没有意义,请耐心等待)鼓励“自上而下”的设计:即,当我进行 merge_sort 时,您首先写下所有合并排序步骤,并将它们命名为单独的辅助函数;然后一一实现这些辅助函数(如果需要进一步划分这些辅助函数,请以相同的方法进行)。

这似乎有点违背OO设计,在OO设计中你可以从底层开始构建基本的数据结构,然后将数据结构和算法组装成你想要的东西。

感谢您的评论。是的,我想获得有关如何“用函数式语言思考”的建议(就像“用 Java 思考”、“用 C++ 思考”)。

As a newbie in functional languages (I started touching Erlang a couple of weeks ago -- the first functional language I could get my hands on).

I started to writing some small algorithms (such as left_rotate_list, bubble_sort, merge_sort etc.). I found myself often getting lost in decisions such as "should I use a helper List for intermediate result storage?" and "should I create a helper function to do this?"

After a while, I found that functional programming (bear with me if what I am talking does not make sense at all) encourages a "top down" design: i.e., when I do merge_sort, you first write down all the merge sort steps, and name them as individual helper functions; and then you implement those helper functions one by one (and if you need to further dividing those helper functions, do it in the same approach).

This seems to contradict OO design a little, in which you can start from the bottom to build the basic data structure, and then assemble the data structure and algorithms into what you want.

Thanks for comments. Yes, I want to get advice about how to "think in functional language" (just like "thinking in Java", "thinking in C++").

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

小兔几 2024-08-14 13:38:52

答案是函数式编程是使用数学中定义的函数进行编程(简而言之,将值从域映射到共域的无副作用的东西)。实际上将其转化为“如何思考”是一个挥手的部分,很难详尽无遗,但我将举例说明我的一些想法:

  1. 定义比效率更重要。也就是说,人们可以理解其所有行为的明显正确的函数实现比难以推理的复杂优化函数要好。 (并且应该尽可能长时间地被首选;直到有证据表明我们必须打破这一良好的性质。)
  2. 数学函数没有副作用。一个有用的程序必然有副作用。函数式程序员意识到副作用是一件非常危险和复杂的事情,并将程序设计为一堆函数,这些函数从一个副作用中获取输出值,并为下一个副作用创建输入值。

第一个与模糊相关:“优雅的代码”。列表推导式可以呈现非常简洁的数学方程,例如函数的定义。只要看一下用 LC 实现的快速排序即可。这就是我对优雅、简洁、让所有行为变得清晰的定义。不是 Perl 代码高尔夫,您通常会简洁而神秘。

第二点是我在所有编程中日常使用的东西。将代码划分为当前状态的函数(方法、例程等),这些函数是无副作用的计算,为要采取的下一个操作提供输入(即使下一个要采取的操作是)。返回值后,将其交给执行所描述操作的例程,然后重新开始。

在我的脑海中,我将 Erlang 过程绘制为状态机图,其中每个顶点都是一个副作用,也是一个函数,其输出是从顶点中选择哪条边。函数式编程范式教会了我对副作用的高度重视。特别是在 Erlang 中,因为副作用在并发性中确实很重要,而 Erlang 使并发性变得非常可用。

同样,一些孤立的部落只有一个词来表示 3 以上的数字,或者没有词来表示“我的”/“你的”。感觉流行语言没有“这会导致副作用”这样的词语,但函数式编程有。它迫使你一直意识到这一点,这是一件好事。

An answer is that functional programming is to program using functions, as they are defined in mathematics (in short, side-effect free things that map values from the domain to the codomain). To actually translate that into "how to think" is the hand-waving part that is difficult to be exhaustive about, but I'll sample some of my thoughts:

  1. The definition is more important than the efficiency. That is, an obviously correct implementation of a function that one can understand all of the behaviour of is better than a complex optimized one that is hard to reason about. (And should be preferred as long as possible; until there is evidence one must break this nice property.)
  2. A mathematical function has no side-effects. A useful program must have side-effects. A functional programmer is aware of side effects, as a very dangerous and complicating thing, and designs the program as a bunch of functions that take output values from one side-effect and creates input values to the next side-effect.

Number one is associated with the vague: "elegant code". List comprehensions can present very succinct and mathematical-equations like definitions of functions. Just look at the quick-sort implemented with LCs. This is how I define elegance, succinct and makes all behaviours clear. Not that perl code-golf where you are most often terse and cryptic.

Number two is something that I use day to day in all programming. Divide code into functions (methods, routines, etc...) of current state that are side-effect free computations giving inputs to the next action to take (even which the next action to take is). When the value is returned, give it to a routine that performs the action that is described, then start over.

In my head I diagram an Erlang process as a state machine graph, where each vertex is a side-effect and a function whose output is which edge to chose out of the vertex. The high regard of side-effects is something the functional programming paradigm taught me. Especially in Erlang, since side-effects really matter in concurrency, and Erlang makes concurrency very available.

The same way some isolated tribes have only one word for numbers above 3, or no words for "mine"/"yours". It feels like popular languages do not have words for "this will cause a side-effect", but Functional Programming has it. It is forcing you to be aware of it all the time, and that is a good thing.

病毒体 2024-08-14 13:38:52

过了一段时间,我发现函数式编程[...]鼓励“自上而下”的设计。

嗯,这实际上并不是“自上而下”或“自下而上”的设计。这是关于关注手头问题的“什么”,而不是“如何”。当我开始函数式编程时,我发现我一直在回忆命令式结构,比如 C 中的嵌套 for 循环。然后我很快发现,尝试将我的命令式思维转化为函数式结构非常困难。我将尝试给你一个更具体的例子。我将用 C 和 Haskell 实现一个等效的程序,并尝试在这两种情况下追踪我的思维过程。请注意,为了解释的目的,我已经明确地冗长了。

C 中:

#include <stdio.h>

int main(void)
{
  int i, inputNumber, primeFlag = 1;
  scanf("%d", &inputNumber);
  for(i = 2; i <= inputNumber / 2; i ++)
    {
      if (inputNumber % i == 0)
        {
          primeFlag = 0;
          break;
        }
    }
  if (primeFlag == 0) printf("False\n");
  else printf ("True\n");
  return 0;
}

我的思维过程轨迹:

  1. 分步思考。首先,接受用户的号码。让这个数字称为inputNumber。 scanf() 写入。
  2. 基本算法:除非另有证明,否则数字是素数。 primeFlag 已声明并设置为 1
  3. 检查 primeNumber 与从 2 到 primeNumber/2 之间的每个数字。 for 循环开始。声明了一个循环变量i来检查primeNumber
  4. 为了反驳我们最初关于该数字是质数的断言,请对照每个 i 检查 primeNumber 。当我们找到一个 i 整除 primeNumber 时,将 primeFlag 设置为 0break.循环体已写好。
  5. for 循环中完成严格的检查过程后,检查 primeFlag 的值并将其报告给用户。 printf() 写入。

在 Haskell 中:

assertPrime :: (Integral a) => a -> Bool
assertPrime x = null divisors
    where divisors = takeWhile (<= div x 2) [y | y <- [2..], mod x y == 0]

我的思维过程的轨迹:

  1. 如果一个数字除了 1 和它本身之外没有约数,那么它就是素数。因此,空除数
  2. 我们如何构建除数?首先,让我们写下可能的候选人名单。写下德克萨斯州从 2 到数字/2 的范围。
  3. 现在,过滤列表,并仅挑选出真正是该数字的约数的项目。编写过滤器 mod xy == 0

我想获得有关如何做的建议
“用函数式语言思考”

好吧,首先也是最重要的,思考“什么”,而不是“如何”。这可能需要大量练习才能习惯。另外,如果您像我一样以前是一名 C/C++ 程序员,请不要再担心内存问题了!现代语言有一个垃圾收集器,它是为您使用而编写的 - 所以甚至不要尝试就地修改变量。另一件对我个人有帮助的事情是:在程序中写下类似英语的定义,以抽象出执行繁重工作的函数。

After a while, I found that functional programming [...] encourages a "top down" design.

Well, it's not about "top down" or "bottom up" design really. It's about focusing on the "what" of the problem at hand, rather than the "how". When I started off with functional programming, I found that I kept recalling imperative constructs like the nested for loop in C. Then I quickly found out that trying to translate my imperative thinking to functional constructs was very difficult. I'll try to give you a more concrete example. I'll implement an equivalent program in C and Haskell and attempt to trace my thought process in both cases. Note that I've been explicitly verbose for the purpose of explanation.

In C:

#include <stdio.h>

int main(void)
{
  int i, inputNumber, primeFlag = 1;
  scanf("%d", &inputNumber);
  for(i = 2; i <= inputNumber / 2; i ++)
    {
      if (inputNumber % i == 0)
        {
          primeFlag = 0;
          break;
        }
    }
  if (primeFlag == 0) printf("False\n");
  else printf ("True\n");
  return 0;
}

Trace of my thought process:

  1. Think in steps. First, accept a number from the user. Let this number be called inputNumber. scanf() written.
  2. Basic algorithm: A number is prime unless otherwise proven. primeFlag declared and set equal to 1.
  3. Check primeNumber against every number from 2 to primeNumber/2. for loop started. Declared a loop variable i to check primeNumber against.
  4. To disprove our initial assertion that the number is prime, check primeNumber against each i. The moment we find even one i that divides primeNumber, set primeFlag to 0 and break. Loop body written.
  5. After going through our rigorous checking process in the for loop, check the value of primeFlag and report it to the user. printf() written.

In Haskell:

assertPrime :: (Integral a) => a -> Bool
assertPrime x = null divisors
    where divisors = takeWhile (<= div x 2) [y | y <- [2..], mod x y == 0]

Trace of my thought process:

  1. A number is prime if it has no divisors but one and itself. So, null divisors.
  2. How do we build divisors? First, let's write down a list of possible candidates. Wrote down Texas range from 2 to number/2.
  3. Now, filter the list, and pick out only items that are really divisors of the number. Wrote the filter mod x y == 0

I want to get advice about how to
"think in functional language"

Ok, first and foremost, think "what", not "how". This can take a lot of practice to get used to. Also, if you were formerly a C/C++ programmer like me, stop worrying about memory! Modern languages have a garbage collector, and it's written for you to use- so don't even try to modify variables in place. Another thing that has personally helped me: write down English-like definitions in your program to abstract out the functions that do the heavy-lifting.

情释 2024-08-14 13:38:52

过了一段时间,我发现函数式编程[...]鼓励“自上而下”的设计。

我不确定这是一个准确的说法。我最近一直在尝试自学函数式编程,我发现某种“自下而上”的编程风格确实对我有帮助。要使用合并排序的示例:

  • 首先查看基本情况。如何对 0/1 元素的数组进行排序?
  • 接下来,看看 base + 1、base + 2、… 的情况。最终,您应该看到一种模式(拆分为子问题、解决子问题、组合子解决方案),该模式允许您编写一般的递归情况,然后最终到达基本情况。
  • 分解子问题很容易,但组合子解决方案有点困难。您需要一种方法将两个排序数组合并为一个排序数组。
  • 现在把所有东西放在一起。恭喜,您刚刚编写了合并排序。 :)

我可能误用了这个术语,但这对我来说感觉像是自下而上的设计。函数式编程与面向对象编程不同,但在两者之间切换时,您不需要完全放弃现有的设计技术。

After a while, I found that functional programming […] encourages a "top down" design.

I'm not sure this is an accurate statement. I've been recently trying to teach myself functional programming, and I've found that a sort "bottom-up" style of programming really helps me. To use your example of merge sort:

  • First look at the base case. How do you sort an array of 0/1 elements?
  • Next, look at the base + 1, base + 2, … cases. Eventually, you should see a pattern (splitting into subproblems, solving subproblems, combining subsolutions) that allows you to write a general recursive case than eventually reaches the base case.
  • Splitting into subproblems is easy, but combining the subsolutions is a bit harder. You need a way to merge two sorted arrays into one sorted array.
  • Now put everything together. Congratulations, you've just written merge sort. :)

I could be misusing the term, but this feels like bottom-up design to me. Functional programming is different than object-oriented programming, but you shouldn't need to totally abandon existing design techniques when switched between the two.

疯到世界奔溃 2024-08-14 13:38:52

我发现自己经常迷失在诸如“我应该使用辅助列表来存储中间结果吗?”之类的决策中。和“我应该创建一个辅助函数来执行此操作吗?”

我对此的建议:阅读小阴谋家。你可以在 Erlang 中关注它。这是一本好书,可以让你体会到这一点。

I found myself often getting lost in decisions such as "should I use a helper List for intermediate result storage?" and "should I create a helper function to do this?"

My advice for this: read The Little Schemer. You can follow it in Erlang. It's a good book to drill a sense of this into you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文