简体   繁体   中英

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

How do I convert this JavaScript code to Clojure?

I am trying to draw a (x,y) world where the cells are on or off according to the fill property. In the example below I am trying to print the rows then columns but my next step is to move the fill property around (up, down, left, right). So, I don't want an answer which wouldn't work if I were not printing the data structure.

My goal is to understand how to think about this problem in a functional way. It was easy for me to solve this problem with mutable state in JavaScript. While working on the solution, I was able to easily reason about how to write the code in JavaScript but when I came to do the same in Clojure, I was at a loss. So, I feel like a good answer to this question would be to help me understand how to think about this kind of problem in a functional way.

I have watched many talks and read a bunch of articles about Clojure and functional programming but when it becomes to writing the code, it's difficult for me to get started.

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);

The output looks like:

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

EDIT: After spending time working through the problems at 4clojure.com, I realized that I was trying to bite off a larger problem (ex. a Chess game in Clojure) than I was ready to solve. Creating the most basic functions at 4clojure.com has been difficult but it's building a steady working knowledge on how to approach a solution using the functional method.

How to start thinking in a functional way? There is no short cuts and quick answers. You have to invest a good amount of time trying to code in functional way.

You can start by morphing simple algorithms. But it is important to keep in mind that in functional programming you care about data flow, not how to instruct the CPU to do this and that.

It is important to know the core functions in your language. From those core functions, you can start transform you data. Kind of Lego or DNA stripes if you want.

If you are interested in Clojure particularly, then I recommend to spend good time on 4Clojure and Land of Lisp .

And Clojure IRC is a wonderful place to learn from Rockstars Clojure developers. Friendly community and helpful for sure.

Always remember: "OOP isn't easy by default and FP isn't hard by default".

Well the data will look more or less identical:

(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}]])

but for the print function you can use doseq

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

(print-world world)

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

and to change elements, assoc-in or update-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 isn't the best fit for this kind of programming, but it is useful stuff to know.

Edit: as for thinking in a functional way, that's not the kind of skill that can be easily conveyed through a stackoverflow answer. It takes a good deal of writing code and reading other people's code. A great place to start online would be Clojure for the Brave and True . The thing that got me thinking functionally was the wonderful Learn you a Haskell for Great Good! .

When trying to get into "the functional way of thinking" it sometimes helps to look at code and think of ways to . In this example there are three logical phases:

  • change the data to contain only the filter field
  • put it in vector form (with the [] s)
  • print it nicely

If we seperate these into their own Clojure expressions then perhaps you will be able to reuse one of them elsewhere, or write better tests etc. as opposed to doing both actions in the same pass through the matrix (which is admittedly, slightly more efficient)

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

This sketch generates the states of the world rather than mutating a structure in memory. In Rich Hickey speak, it generates the next value instead of storing something different at the same old place.

// 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)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM