【问题标题】:Functional Programming for Basic Algorithms基本算法的函数式编程
【发布时间】:2010-12-04 21:04:47
【问题描述】:

“纯”函数式编程对于基本例程实现有多好,例如列表排序、字符串匹配等?

在任何函数式语言的基本解释器中实现这些基本功能是很常见的,这意味着它们将用命令式语言 (c/c++) 编写。虽然有很多例外..

至少,我想问一下:在使用“纯”函数式语言编码时模仿命令式风格有多难?

【问题讨论】:

  • 您是在问在用另一种风格写作的同时模仿一种风格有多难?
  • 函数式语言将使用命令式实现的假设是值得怀疑的。 OCaml 是用 OCaml 编写的,最流行的 Haskell (GHC) 实现是用 Haskell 编写的。
  • @ShreevatsaR:也许我应该换个说法,但这就是我要问的。在没有像“progn”、“do”这样的特殊结构的情况下,用纯函数式语言编写命令式程序是多么困难……例如,函数式程序可以通过使用闭包来模拟“命令式”状态是一个已知的技巧。这就是我要问的。
  • 功能性和命令性是对立的观念需要打消。 Haskell 是纯函数式的,它也是一种非常好的命令式编程语言。

标签: functional-programming


【解决方案1】:

“纯”功能有多好 基本程序的编程 实现,例如列表排序, 字符串匹配等?

非常。我会在 Haskell 中解决你的问题,我会稍微详细一点。我的目的不是让你相信这个问题可以用 5 个字符来解决(它可能在 J 中可以!),而是让你了解这些结构。

import Data.List -- for `sort`
stdlistsorter :: (Ord a) => [a] -> [a]
stdlistsorter list = sort list

使用来自Data.Listsort 函数对列表进行排序

import Data.List -- for `delete`
selectionsort :: (Ord a) => [a] -> [a]
selectionsort [] = []
selectionsort list = minimum list : (selectionsort . delete (minimum list) $ list)

选择排序实现。

quicksort :: (Ord a) => [a] -> [a]  
quicksort [] = []  
quicksort (x:xs) =   
    let smallerSorted = quicksort [a | a <- xs, a <= x]  
        biggerSorted = quicksort [a | a <- xs, a > x]
    in  smallerSorted ++ [x] ++ biggerSorted  

快速排序实现。

import Data.List -- for `isInfixOf`
stdstringmatch :: (Eq a) => [a] -> [a] -> Bool
stdstringmatch list1 list2 = list1 `isInfixOf` list2

使用来自Data.listisInfixOf 函数进行字符串匹配

实现这样的基本功能很常见 基本解释器中的函数 任何函数式语言,其中 意味着它们将被写成 命令式语言 (c/c++)。虽然 有很多例外..

视情况而定。有些函数更自然地以命令式的方式表达。不过,我希望我已经让您相信,有些算法也可以自然地以函数的方式表达。

至少,我想问:有多难 是模仿命令式风格吗 在“纯”功能编码时 语言?

这取决于你在 Haskell 中找到 Monad 的难度。就个人而言,我觉得很难掌握。

【讨论】:

【解决方案2】:

1) 按什么标准好?你想要什么属性?

列表排序?简单。让我们在 Haskell 中进行快速排序:

sort [] = []
sort (x:xs) = sort (filter (< x) xs) ++ [x] ++ sort (filter (>= x) xs)

此代码的优点是非常容易理解。如果列表为空,则对其进行排序。否则,调用第一个元素 x,找到小于 x 的元素并对它们进行排序,找到大于 x 的元素并对它们进行排序。然后将排序列表与中间的 x 连接起来。尝试让 C++ 看起来易于理解。

当然,Mergesort 对链表进行排序要快得多,但代码也长了 6 倍。

2) 在保持纯粹功能的同时实现命令式风格非常容易。命令式风格的本质是动作的顺序。使用 monad 在纯设置中对操作进行排序。 monad 的本质是绑定函数:

(Monad m) => (>>=) :: m a -> (a -> m b) -> m b

这个函数存在于C++中,它被称为;

例如,Haskell 中的一系列动作是这样编写的:

putStrLn "What's your name?" >>=
  const (getLine >>= \name -> putStrLn ("Hello, " ++ name))

一些语法糖可以使这看起来更加必要(但请注意,这是完全相同的代码):

do {
  putStrLn "What's your name?";
  name <- getLine;
  putStrLn ("Hello, " ++ name);
}

【讨论】:

  • 我认为对于快速排序,您需要filter 而不是find。对于sort [4, 1, 7, 0],第一个find 将返回Just 1,而filter 将返回[1, 0]
  • 您的快速排序实现不正确 - 无法匹配预期类型 [a]' against inferred type Maybe a1'。
  • 对不起,阿塔尼翁。 s/查找/过滤/g
  • 很好的答案,但是命令式编程的本质不是关于程序的顺序执行,而是关于操作状态。无论如何,您的 quicksort 示例很好。
  • Bubba88,命令式编程是关于“做这个,然后做那个”。这是关于当务之急。碰巧的是,如果不对某种状态进行排序,您就无法“做”任何有趣的事情。
【解决方案3】:

几乎所有函数式编程语言都有一些结构允许命令式编码(如 Haskell 中的 do)。有许多问题域是“纯”函数式编程无法解决的。其中之一是网络协议,例如您需要一系列按正确顺序的命令。而且这些东西不适合纯函数式编程。

我必须同意Lothar,不过,列表排序和字符串匹配并不是您需要强制解决的真正示例。这类事情有众所周知的算法,它们已经可以用函数式语言有效地实现。

【讨论】:

  • 我明白了。我不想提及根据定义需要“状态”和“命令性”的情况(例如您的网络协议示例);只是基本的计算任务,其目的是计算函数。那么,目前的提议是我们可以很容易地为这些事情编写一个纯函数算法?
  • 你可以,但如果你愿意的话,你可以(几乎)纯粹地编写它。我的示例只是突出了命令式样式绝对需要以使其正常工作的一些内容。没有什么能阻止您在其他事情上使用相同的机制。
【解决方案4】:

我认为“算法”(例如方法体和基本数据结构)是函数式编程最好的地方。假设没有完全依赖 IO/状态,函数式编程擅长的是创作算法和数据结构,通常会产生比命令式解决方案更短/更简单/更干净的代码。 (不要模仿命令式风格,FP 风格更适合大多数此类任务。)

您有时需要命令式的东西来处理 IO 或低级性能,并且您希望 OOP 用于划分大型程序的高级设计和架构,但在您编写大部分代码的“小”中, FP是胜利。

另见

How does functional programming affect the structure of your code?

【讨论】:

  • 这篇文章和其中的例子对我来说不太有说服力,但这是我的看法。无论如何,感谢您提供参考。
【解决方案5】:

反之,用命令式风格模拟函数式效果很好。

请记住,解释器或 VM 的内部非常接近金属和性能至关重要,以至于您甚至应该考虑进入汇编级别并计算每条指令的时钟周期(例如 Smalltalk Dophin 正在这样做,结果是感人的)。 CPU 势在必行。

但是完成所有基本算法实现没有问题 - 你提到的那个是NOT低级 - 它们是基础。

【讨论】:

    【解决方案6】:

    我不知道列表排序,但是如果在编译器或运行时中没有某种字符串匹配,你会很难引导一种语言。因此,您需要该例程来创建语言。由于两次编写相同的代码没有多大意义,因此当您创建用于匹配该语言中的字符串的库时,您会调用之前编写的代码。这种情况在后续版本中发生的程度将取决于语言的自托管程度,但除非这是一个强大的设计目标,否则没有任何理由对其进行更改。

    【讨论】:

    • 是的,我意识到需要许多内置功能;但我的实际问题是:它们是否都需要用 C/C++ 编写(这是必要的)?抱歉更正。
    • 不,你可以用汇编程序编写它们,或者平台已经支持的任何其他东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-19
    • 2017-09-19
    • 1970-01-01
    • 2017-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多