如何将这些代码和我的想法转换为功能思维(Clojure)

How do convert this code and my thinking to a functional mindset (Clojure)?

本文关键字:功能 转换 Clojure 代码 我的      更新时间:2024-03-17

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

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

我的目标是了解如何以功能的方式思考这个问题。对我来说,用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上创建最基本的函数一直很困难,但它正在积累如何使用函数方法来处理解决方案的稳定知识。

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

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

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

如果你对Clojure特别感兴趣,那么我建议你花点时间在4Clojure和Lisp之地上。

Clojure IRC是向Rockstar Clojure开发人员学习的好地方。友好的社区和乐于助人的肯定。

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

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

(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不是最适合这种编程的,但它是有用的东西。

编辑:就功能性思维而言,这不是一种可以通过堆叠式答案轻松传达的技能。它需要大量的代码编写和阅读他人的代码。一个很好的在线起点是Clojure for the Brave and True。让我从功能上思考的是"为美好的未来学习哈斯克尔"!。

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

  • 将数据更改为仅包含筛选字段
  • 以矢量形式(使用[] 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

这个草图生成世界的状态,而不是改变内存中的结构。用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)
}