【问题标题】:How do convert this code and my thinking to a functional mindset (Clojure)?如何将此代码和我的想法转换为功能性思维方式(Clojure)?
【发布时间】:2013-12-05 14:37:47
【问题描述】:

如何将此 JavaScript 代码转换为 Clojure?

我正在尝试根据填充属性绘制一个 (x,y) 世界,其中单元格打开或关闭。在下面的示例中,我尝试先打印行然后打印列,但下一步是移动填充属性(上、下、左、右)。所以,如果我不打印数据结构,我不想要一个不起作用的答案。

我的目标是了解如何以实用的方式思考这个问题。我很容易用 JavaScript 中的可变状态来解决这个问题。在研究解决方案时,我能够很容易地推断出如何用 JavaScript 编写代码,但是当我开始在 Clojure 中做同样的事情时,我不知所措。所以,我觉得这个问题的一个很好的答案是帮助我理解如何以一种功能性的方式思考这类问题。

我看过很多关于 Clojure 和函数式编程的演讲并阅读了很多文章,但是当涉及到编写代码时,我很难开始。

var world = [
    [{ x: 1, y: 4, fill:0 }, { x: 2, y: 4, fill:0 }, { x: 3, y: 4, fill:0 }, { x: 4, y: 4, fill:0 }],
    [{ x: 1, y: 3, fill:0 }, { x: 2, y: 3, fill:0 }, { x: 3, y: 3, fill:0 }, { x: 4, y: 3, fill:0 }],
    [{ x: 1, y: 2, fill:0 }, { x: 2, y: 2, fill:0 }, { x: 3, y: 2, fill:0 }, { x: 4, y: 2, fill:0 }],
    [{ x: 1, y: 1, fill:0 }, { x: 2, y: 1, fill:0 }, { x: 3, y: 1, fill:1 }, { x: 4, y: 1, fill:0 }]
];

function printworld(world) {
    var out = "";
    for(var row=0; row<world.length; row++) {
        for(var col=0; col<world[row].length; col++) {
            out += "["+world[row][col].fill+"]";
        }
        out += "\n"
    }
    console.log(out);
}

printworld(world);

输出如下:

[0][0][0][0]
[0][0][0][0]
[0][0][0][0]
[0][0][1][0]

编辑:在 4clojure.com 上花时间解决问题后,我意识到我正试图解决一个比我准备解决的更大的问题(例如 Clojure 中的国际象棋游戏)。在 4clojure.com 上创建最基本的函数很困难,但它正在建立关于如何使用函数方法处理解决方案的稳定工作知识。

【问题讨论】:

  • 您是否正在寻找等效的 Clojure 代码或如何开始考虑以 Clojure 方式解决它?
  • 你可以在 js 中使用两个命名方法来映射()数组:renderRow 和 renderCell...
  • Chiron,我希望开始考虑以 Clojure 方式解决它。当然,更好的思考是我所追求的。

标签: javascript clojure functional-programming


【解决方案1】:

如何开始以实用的方式思考?没有捷径和快速的答案。您必须投入大量时间尝试以功能方式编写代码。

您可以从变形简单算法开始。但重要的是要记住,在函数式编程中,您关心的是数据流,而不是如何指示 CPU 执行此操作。

了解您的语言的核心功能很重要。从这些核心功能,您可以开始转换您的数据。如果您愿意,可以使用乐高或 DNA 条纹。

如果您特别对 Clojure 感兴趣,那么我建议您花时间在 4Clojure 和 Land of Lisp

Clojure IRC 是向 Rockstars Clojure 开发人员学习的好地方。友好的社区,肯定会有所帮助。

永远记住:“默认情况下 OOP 并不容易,默认情况下 FP 并不难”。

【讨论】:

  • Chiron,感谢“无捷径”和 4clojure 资源中的鼓励。不久前我读了一些 Lisp 的土地。人们说,“它为我点击”。我发现的大部分材料都是我已经阅读过的材料的重复。我相信我正在苦苦挣扎,因为我想要我的可变变量,而我不知道如何在功能上进行不同的思考。当您对问题感到困惑时,您如何提出正确的问题?是时候再次提出“如何解决它”了。
  • Chiron,我给你解决方案是因为你找到了我问题的核心,尽管我问得不是很好(对其他很好的答案表示歉意)。我在 4clojure.com 上花了几个小时,它是一座金矿。我学到了很多东西,作为奖励,我在这个过程中感到鼓舞。谢谢!
【解决方案2】:

好吧,数据看起来或多或少是一样的:

(def world [[{:x 1, :y 2, :fill 0}, {:x 2, :y 2, :fill 0}]
            [{:x 1, :y 1, :fill 0}, {:x 2, :y 2, :fill 1}]])

但对于打印功能,您可以使用doseq

(defn print-world [world]
  (doseq [row world]
    (doseq [cell row]
      (print (str "[" (:fill cell) "]")))
    (println)))

(print-world world)

;; outputs
;; [0][0]
;; [0][1]

要更改元素,assoc-inupdate-in

; move the filled cell 'up'
(print-world
  (-> world
    (assoc-in [1 1 :fill] 0)     ; set bottom-right fill to 0
    (assoc-in [0 1 :fill] 1)))   ; set top-right fill to 1

;; outputs
;; [0][1]
;; [0][0]

Clojure 不是这种编程的最佳选择,但了解它很有用。

编辑:至于以功能方式思考,这不是可以通过 stackoverflow 答案轻松传达的技能。它需要大量的编写代码和阅读其他人的代码。一个在线开始的好地方是Clojure for the Brave and True。让我在功能上思考的是美妙的Learn you a Haskell for Great Good!

【讨论】:

  • d.j.sheldrick,感谢您及时和出色的回复。我今天晚些时候再回来看看。
【解决方案3】:

当尝试进入“功能性思维方式”时,有时查看代码并考虑分离可分离部分的方法会有所帮助。在这个例子中,有三个逻辑阶段:

  • 将数据更改为仅包含筛选字段
  • 以矢量形式(使用[]s)
  • 打印得很好

如果我们将它们分离成它们自己的 Clojure 表达式,那么也许您可以在其他地方重用它们中的一个,或者编写更好的测试等,而不是通过矩阵在同一次传递中执行这两个操作(诚然,稍微更高效)

user> (def world [[{:x 1, :y 4, :fill 0 }, {:x 2, :y 4, :fill 0 }, {:x 3, :y 4, :fill 0 }, {:x 4, :y 4, :fill 0 }],
                  [{:x 1, :y 3, :fill 0 }, {:x 2, :y 3, :fill 0 }, {:x 3, :y 3, :fill 0 }, {:x 4, :y 3, :fill 0 }],
                  [{:x 1, :y 2, :fill 0 }, {:x 2, :y 2, :fill 0 }, {:x 3, :y 2, :fill 0 }, {:x 4, :y 2, :fill 0 }],
                  [{:x 1, :y 1, :fill 0 }, {:x 2, :y 1, :fill 0 }, {:x 3, :y 1, :fill 1 }, {:x 4, :y 1, :fill 0 }]])
#'user/world

user> (defn filter-fill [world] (map #(map :fill %) world))
#'user/filter-fill

user> (filter-fill world)                                                            
((0 0 0 0) (0 0 0 0) (0 0 0 0) (0 0 1 0))

user> (defn vector-world [world]  (mapv #(mapv vector %) world))
#'user/vector-world 

user> (clojure.pprint/pprint (vector-world (filter-fill world)))
[[[0] [0] [0] [0]]
 [[0] [0] [0] [0]]
 [[0] [0] [0] [0]]
 [[0] [0] [1] [0]]]
nil

【讨论】:

  • 纯美优雅!怎么会有人不爱上 Clojure?
  • 您可以使用mapvcomp 来简化此操作。例如(defn vector-world [world] (mapv #(mapv (comp vector :fill) %) world))
  • 是的,这在向量上要好得多。这个答案的主要思想是尽可能分离地显示事物,因此将它们组合起来,虽然更好的clojure,但不能很好地说明这一点,所以我把comp排除在外。
  • 亚瑟,感谢您的帮助。我很欣赏您展示如何将问题分解为更小的问题的方式。
  • Arthur,在 4clojure.com 上花了很多时间后,我明白了,“分离可分离的部分”是关键。再次感谢!
【解决方案4】:

这个草图生成了世界的状态,而不是改变内存中的结构。用 Rich Hickey 的话说,它生成下一个值,而不是在同一个旧位置存储不同的东西。

// world is an object with the world's state
var world = function(tl, tr, bl, br){
    this.state = {topLeft: tl, topRight: tr, bottomLeft: bl, bottomRight: br};
};

// screen is an object with internal state that represents
// the state of the of the world
var screen = function(aWorld){
    this.state = [[{fill: aWorld.state.topLeft},
           {fill: aWorld.state.topRight}],
          [{fill: aWorld.state.bottomLeft},
           {fill: aWorld.state.bottomRight}]];
};

// showScreen is a function in which all side
// effects occur.
function showScreen(aScreen){
    var out = "";
    for (var line = 0; line < aScreen.state.length; line++){
    for (var pix = 0; pix < aScreen.state[line].length; pix++){
        out += "[" + aScreen.state[line][pix].fill + "]";
    }
    out += "\n"
    }
    console.log(out);
}

// here is the heart of functional style programming:
// generating the next value instead of changing
// some existing state
function nextWorld(tr,tl,br,bl){
    showScreen(new screen(new world(tr,tl,br,bl)))
}

// turn off screen
function screenOff(){
    nextWorld(0,0,0,0)
}

// make a forward slash
function forwardSlash(){
    nextWorld(1,0,0,1)
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-25
    • 1970-01-01
    • 2012-06-18
    • 1970-01-01
    • 2016-05-01
    相关资源
    最近更新 更多