Functional programming 什么时候可以用函数式语言修改变量?

Functional programming 什么时候可以用函数式语言修改变量?,functional-programming,scheme,lisp,purely-functional,mutability,Functional Programming,Scheme,Lisp,Purely Functional,Mutability,所以,我正在自学使用racketscheme的函数式编程,到目前为止我很喜欢它。作为我自己的练习,我一直在尝试以一种纯功能的方式实现一些简单的任务。我知道不变性是函数式风格的一个重要组成部分,但我想知道是否有这样的情况 我想到了一种有趣的方法,当与过滤器一起使用时,函数可以从字符串列表中去除非唯一字符串,如下所示: (define (make-uniquer) (let ([uniques '()]) (lambda (x) (if (not (member x uniq

所以,我正在自学使用racketscheme的函数式编程,到目前为止我很喜欢它。作为我自己的练习,我一直在尝试以一种纯功能的方式实现一些简单的任务。我知道不变性是函数式风格的一个重要组成部分,但我想知道是否有这样的情况

我想到了一种有趣的方法,当与过滤器一起使用时,函数可以从字符串列表中去除非唯一字符串,如下所示:

(define (make-uniquer)
  (let ([uniques '()])
    (lambda (x)
      (if (not (member x uniques))
          (set! uniques (cons x uniques))
          #f))))

(define (uniquify x)
  (let ([uniquer (make-uniquer)])
    (filter uniquer x)))
正如您所看到的,
make uniquer
在字符串列表上返回一个闭包以进行唯一性比较,这样它就可以充当过滤器的简单谓词。但我正在破坏性地更新关闭列表。这是一种糟糕的形式,还是可以用这种方式改变局部封闭变量?

纯函数编程和非纯函数编程 s是固有的,这允许记忆(缓存结果)。缺少可变状态允许重入,允许不同版本的链接数据结构共享内存,并使自动并行成为可能。关键是,通过限制自己的变异状态,您不再需要考虑命令式编程的许多复杂问题

然而,这种限制也有缺点。一个是性能:一些算法和数据结构(如构建哈希表)不能简单地表示为纯函数,而不必复制大量数据。另一个:与Haskell相比,Haskell是一种纯函数式语言。由于变异(概念上)不存在,所以必须使用s来表示状态变化。(虽然Haskell提供了一种相当简洁的
do
-符号语法糖,但在状态单子中编程与“常规”Haskell是完全不同的语言!)如果您的算法最容易用几个改变状态的互锁循环来表示,Haskell实现将比不纯正语言中的实现更加笨拙

例如,更改XML文档中深度嵌套的单个节点。这是可能的,但如果没有状态突变,使用。伪代码示例(纯):

old_xml=。
(此外,通常可以编写一个只处理内部状态的过程函数,这样就可以对其进行包装,使其对调用方来说实际上是一个纯函数。这在不纯净的语言中更容易一些,因为您不必在状态单子中编写它并将其传递给
runST

虽然以不纯正的风格编写会失去这些好处,但函数编程的其他一些便利(如高阶函数)仍然适用

利用突变 Lisp是一种函数语言,这意味着它允许状态变异这是经过设计的,因此,如果您需要变异,您可以使用它,而实际上不必使用其他语言

通常情况下,是的,在

  • 出于性能原因需要,或者
  • 使用变异可以更清楚地表达您的算法
当您这样做时:

  • 清楚地记录您的
    uniquify
    函数将改变您传递给它的列表。调用者给你的函数传递一个变量,然后让它变回来,这将是一件令人讨厌的事情
  • 如果您的应用程序是多线程的,请分析、注意并记录您的不纯函数是否是线程安全的

在这种情况下,我会避免可变实现,因为功能实现在性能方面可以很好地竞争。以下是该函数的三个版本(包括内置的
删除重复项
):

#lang racket

(define (make-uniquer)
  (let ([uniques (make-hash)])
    (lambda (x)
      (if (not (hash-ref uniques x #f))
          (hash-set! uniques x #t)
          #f))))

(define (uniquify x)
  (let ([uniquer (make-uniquer)])
    (filter uniquer x)))

(define (uniquify-2 lst)
  (define-values (_ r)
   (for/fold ([found (hash)] [result '()])
             ([elem (in-list lst)])
     (cond [(hash-ref found elem #f)
            (values found result)]
           [else (values (hash-set found elem #t)
                         (cons elem result))])))
  (reverse r))

(define randoms (build-list 100000 (λ (n) (random 10))))
(time (for ([i 100]) (uniquify randoms)))
(time (for ([i 100]) (uniquify-2 randoms)))
(time (for ([i 100]) (remove-duplicates randoms)))

;; sanity check
(require rackunit)
(check-equal? (uniquify randoms) (uniquify-2 randoms))
(check-equal? (uniquify randoms) (remove-duplicates randoms))
我做这件事的次数是

cpu time: 1348 real time: 1351 gc time: 0
cpu time: 1016 real time: 1019 gc time: 32
cpu time: 760 real time: 760 gc time: 0
这不是科学数字,所以对此持怀疑态度。公平地说,我确实调整了
uniquify-2
,因为我的第一个版本比较慢。我还通过一个哈希表改进了可变版本,但是可能还有其他的优化可以应用。此外,内置的
删除重复项
针对性能进行了优化,并使用了可变的数据结构(尽管它避免了
设置!


你也可能对这本书感兴趣。指出
set
可能会损害性能,因此请小心使用。

如果两个线程同时调用该函数,它是否会中断?@Bill如果两个线程从
make uniquer
使用自己的闭包,则为否。但如果它们都使用相同的闭包,则为是。这是安全的主要测试吗?作为一个工程决策,这取决于你。好问题。如果
make uniquer
仅从(由)(内部)调用
uniquify
,则没有问题。确保它被很好地隐藏、封装。
#lang racket

(define (make-uniquer)
  (let ([uniques (make-hash)])
    (lambda (x)
      (if (not (hash-ref uniques x #f))
          (hash-set! uniques x #t)
          #f))))

(define (uniquify x)
  (let ([uniquer (make-uniquer)])
    (filter uniquer x)))

(define (uniquify-2 lst)
  (define-values (_ r)
   (for/fold ([found (hash)] [result '()])
             ([elem (in-list lst)])
     (cond [(hash-ref found elem #f)
            (values found result)]
           [else (values (hash-set found elem #t)
                         (cons elem result))])))
  (reverse r))

(define randoms (build-list 100000 (λ (n) (random 10))))
(time (for ([i 100]) (uniquify randoms)))
(time (for ([i 100]) (uniquify-2 randoms)))
(time (for ([i 100]) (remove-duplicates randoms)))

;; sanity check
(require rackunit)
(check-equal? (uniquify randoms) (uniquify-2 randoms))
(check-equal? (uniquify randoms) (remove-duplicates randoms))
cpu time: 1348 real time: 1351 gc time: 0
cpu time: 1016 real time: 1019 gc time: 32
cpu time: 760 real time: 760 gc time: 0