当前位置:  开发笔记 > 编程语言 > 正文

您是否发现您仍然需要可以更改的变量,如果是这样,为什么?

如何解决《您是否发现您仍然需要可以更改的变量,如果是这样,为什么?》经验,为你挑选了4个好方法。

我听过的反对函数式语言的一个论点是,单个赋值编码太难了,或者至少比"普通"编程要难得多.

但是通过我的代码,我意识到如果你用一种合理的现代语言写作,我真的没有很多(任何?)使用模式,使用单一的赋值形式也无法编写.

那么变量的用例有哪些在一个单独的范围调用中变化?请记住,在这种情况下,调用之间不同的循环索引,参数和其他范围限制值不是多个赋值(除非您出于某种原因必须在正文中更改它们),并假设您正在编写某些内容远远高于汇编语言级别,在那里你可以编写类似的东西

values.sum

或(如果没有提供金额)

function collection.sum --> inject(zero, function (v,t) --> t+v )

x = if a > b then a else b

要么

n = case s 
  /^\d*$/ : s.to_int
  ''      : 0
  '*'     : a.length
  '?'     : a.length.random
  else    fail "I don't know how many you want"

当你需要,并有列表推导,地图/收集等等.

您是否发现在这样的环境中仍然需要/需要可变变量,如果是,那么对于什么?

澄清一点,我不是要求对SSA表格的异议进行背诵,而是要求适用这些异议的具体例子.我正在寻找具有可变变量的清晰简洁的代码,如果没有它们就无法编写.

到目前为止我最喜欢的例子(以及我对他们的最佳反对意见):

    保罗约翰逊的Fisher-Yates算法答案,当你包含大O约束时,它非常强大.但是,正如catulahoops指出的那样,big-O问题与SSA问题无关,而是与可变数据类型相关联,并且在预留的情况下,可以在SSA中清楚地编写算法:

     shuffle(Lst) ->
         array:to_list(shuffle(array:from_list(Lst), erlang:length(Lst) - 1)).
     shuffle(Array, 0) -> Array;
     shuffle(Array, N) ->
         K = random:uniform(N) - 1,
         Ek = array:get(K, Array),
         En = array:get(N, Array),
         shuffle(array:set(K, En, array:set(N, Ek, Array)), N-1).
    

    jpalecek的多边形示例区域:

    def area(figure : List[Point]) : Float = {
      if(figure.empty) return 0
      val last = figure(0)
      var first= figure(0)
      val ret = 0
      for (pt <- figure) {
        ret+=crossprod(last - first, pt - first)
        last = pt
      }
      ret
    }
    

    可能仍然写得像:

    def area(figure : List[Point]) : Float = {
        if figure.length < 3
            0
          else
            var a = figure(0)
            var b = figure(1)
            var c = figure(2)
            if figure.length == 3
                magnitude(crossproduct(b-a,c-a))
              else 
                foldLeft((0,a,b))(figure.rest)) { 
                   ((t,a,b),c) => (t+area([a,b,c]),a,c)
                   }
    

    或者,由于有些人反对这种配方的密度,它可以重铸:

    def area([])    = 0.0   # An empty figure has no area
    def area([_])   = 0.0   # ...nor does a point
    def area([_,_]) = 0.0   # ...or a line segment
    def area([a,b,c]) =     # The area of a triangle can be found directly
        magnitude(crossproduct(b-a,c-a))
    def area(figure) =      # For larger figures, reduce to triangles and sum
        as_triangles(figure).collect(area).sum
    
    def as_triangles([])      = []  # No triangles without at least three points
    def as_triangles([_])     = []
    def as_triangles([_,_])   = []
    def as_triangles([a,b,c | rest) = [[a,b,c] | as_triangles([a,c | rest])]
    

    公主关于用不可变结构实现O(1)队列的难度的观点很有意思(并且很可能提供一个引人注目的例子的基础),但正如所述,它基本上是关于数据结构的可变性,而不是直接关于多重赋值问题.

    我对Eratosthenes的回答很感兴趣,但不相信.正如你所引用的论文中给出的那样,正确的大O,拉动尽可能多的素数,无论是否有SSA,都不容易正确实现.


好吧,谢谢大家的尝试.由于大多数答案被证明是1)基于可变数据结构,而不是基于单一指派,2)在某种程度上它们是关于单一指派形式容易被本领域技术人员反击,我将要从我的谈话和/或重组中划线(可能在备份中将其作为讨论主题,在不太可能发生的事情中,在我用完时间之前用完了).

再次感谢.



1> Paul Johnson..:

我遇到的最难的问题是改变列表.所述费-耶茨算法(有时也被称为Knuth的算法)涉及迭代通过列表交换的每个项目具有随机其他项目.该算法是O(n),众所周知且长期以来证明是正确的(在某些应用中是一个重要的属性).但它需要可变数组.

这并不是说你不能在功能程序中进行改组.Oleg Kiselyov 写过这篇文章.但是如果我理解正确的话,功能改组是O(n.log n),因为它通过构建二叉树来工作.

当然,如果我需要在Haskell中编写Fisher-Yates算法,我只需将它放在ST monad中,这样就可以在一个漂亮的纯函数中包含一个涉及可变数组的算法,如下所示:

-- | Implementation of the random swap algorithm for shuffling.  Reads a list
-- into a mutable ST array, shuffles it in place, and reads out the result
-- as a list.

module Data.Shuffle (shuffle) where


import Control.Monad
import Control.Monad.ST
import Data.Array.ST
import Data.STRef
import System.Random

-- | Shuffle a value based on a random seed.
shuffle :: (RandomGen g) => g -> [a] -> [a]
shuffle _ [] = []
shuffle g xs = 
    runST $ do
      sg <- newSTRef g
      let n = length xs
      v <- newListArray (1, n) xs
      mapM_ (shuffle1 sg v) [1..n]
      getElems v

-- Internal function to swap element i with a random element at or above it.
shuffle1 :: (RandomGen g) => STRef s g -> STArray s Int a -> Int -> ST s ()
shuffle1 sg v i = do
  (_, n) <- getBounds v
  r <- getRnd sg $ randomR (i, n)
  when (r /= i) $ do
    vi <- readArray v i
    vr <- readArray v r
    writeArray v i vr
    writeArray v r vi


-- Internal function for using random numbers
getRnd :: (RandomGen g) => STRef s g -> (g -> (a, g)) -> ST s a
getRnd sg f = do
  g1 <- readSTRef sg
  let (v, g2) = f g1
  writeSTRef sg g2
  return 

v



2> Jason Cohen..:

如果你想提出学术论点,那么当然在技术上不必多次分配一个变量.证据是所有代码都可以用SSA(单一静态分配)形式表示.实际上,这是用于多种静态和动态分析的最有用的形式.

同时,有理由我们不能以SSA形式编写代码开头:

    以这种方式编写代码通常需要更多语句(或更多行代码).简洁有价值.

    它几乎总是效率低下.是的,我知道你在谈论更高级的语言 - 一个公平的范围 - 但即使在Java和C#的世界里,远离集会,速度也很重要.速度无关紧要的应用很少.

    这并不容易理解.虽然SSA在数学意义上"更简单",但它从常识中更抽象,这在现实世界的编程中很重要.如果你必须非常聪明地理解它,那么它在编程中没有任何地方.

即使在上面的示例中,也很容易发现漏洞.请你的case发言.如果存在确定是否'*'允许的管理选项,以及是否允许使用单独的管理选项,该'?'怎么办?此外,对于整数情况,不允许为零,除非用户具有允许它的系统权限.

这是一个更加真实的分支和条件示例.你能把它写成一个单独的"声明吗?" 如果是这样,你的"陈述"与许多单独的陈述真的不同吗?如果没有,您需要多少个临时只写变量?那种情况明显好于单一变量吗?



3> Norman Ramse..:

我从来没有发现过这种情况.虽然你总是可以创建新名称,就像转换为SSA形式一样,但我实际上发现每个值都有自己的名称是很容易和自然的.像Haskell这样的语言给了我很多关于命名值的选择,以及两个不同的地方来放置名称绑定(letwhere).我发现单一作业形式非常自然而且一点都不困难.

我偶尔会错过能够在堆上指向可变对象的指针.但这些东西没有名字,所以这不是同一个异议.(我还发现,当我在堆上使用可变对象时,我倾向于编写更多错误!)



4> Juliet..:

我认为你会发现最高效的语言可以让你混合功能和命令式的风格,比如OCaml和F#.

在大多数情况下,我可以编写代码,这只是"map x to y,reduce y to z"的长行.在95%的情况下,函数式编程简化了我的代码,但有一个领域不变性显示出来:

实现的容易性和不可变堆栈与不可变队列之间的广泛差异.

堆栈很容易并且与持久性很好地匹配,队列是荒谬的.

不可变队列的最常见实现使用一个或多个内部堆栈和堆栈旋转.好处是这些队列大部分时间都在O(1)中运行,但有些操作将在O(n)中运行.如果您依赖于应用程序中的持久性,那么原则上每个操作都可以在O(n)中运行.当您需要实时(或至少一致)性能时,这些队列并不好.

Chris Okasaki在他的书中提供了不可变队列的实现,他们使用懒惰来实现所有操作的O(1).它是一个非常聪明,相当简洁的实时队列实现 - 但它需要深入了解其底层实现细节,并且它仍然比不可变堆栈更复杂.

相反,我可以使用可变链接列表编写堆栈和队列,这些链接列表在所有操作的恒定时间内运行,并且生成的代码将非常简单.


关于多边形的面积,它易于将其转换为功能形式.我们假设我们有一个这样的Vector模块:

module Vector =
    type point =
        { x : float; y : float}
        with
            static member ( + ) ((p1 : point), (p2 : point)) =
                { x = p1.x + p2.x;
                  y = p1.y + p2.y;}

            static member ( * ) ((p : point), (scalar : float)) =
                { x = p.x * scalar;
                  y = p.y * scalar;}

            static member ( - ) ((p1 : point), (p2 : point)) = 
                { x = p1.x - p2.x;
                  y = p1.y - p2.y;}

    let empty = { x = 0.; y = 0.;}
    let to_tuple2 (p : point) = (p.x, p.y)
    let from_tuple2 (x, y) = { x = x; y = y;}
    let crossproduct (p1 : point) (p2 : point) =
        { x = p1.x * p2.y; y = -p1.y * p2.x }

我们可以使用一些元组魔法来定义我们的区域函数:

let area (figure : point list) =
    figure
    |> Seq.map to_tuple2
    |> Seq.fold
        (fun (sum, (a, b)) (c, d) -> (sum + a*d - b*c, (c, d) ) )
        (0., to_tuple2 (List.hd figure))
    |> fun (sum, _) -> abs(sum) / 2.0

或者我们可以使用交叉产品

let area2 (figure : point list) =
    figure
    |> Seq.fold
        (fun (acc, prev) cur -> (acc + (crossproduct prev cur), cur))
        (empty, List.hd figure)
    |> fun (acc, _) -> abs(acc.x + acc.y) / 2.0

我发现这两种功能都不可读.


都在O(1)中运行,但有些操作将在O(n)中运行.如果您依赖于应用程序中的持久性,那么原则上每个操作都可以在O(n)中运行.当您需要实时(或至少一致)性能时,这些队列并不好.
推荐阅读
135369一生真爱_890
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有