Как убрать изменчивость при создании растущего значения, например позиции? - PullRequest
0 голосов
/ 11 ноября 2018

Я пытаюсь использовать более функциональный подход в моем коде, и у меня есть переменная posx, которая представляет позицию на оси X.

меняется со временем xpos = xpos + pos:

const createCircle = ({xpos, fill}) => {
 return {
   move: (pos) => {
     xpos = xpos + pos
     noStroke()
     ellipse( xpos,10,10,10)
   }
 }
} 

Как я не могу изменить переменную xpos?

Я читал, что мутировать переменные нехорошо ... так как мне тогда это сделать?

Или это нормально в этом случае?

Остальной код:

const circleOpts = {
  xpos: 20,
  fill: 0,
}

const createCircle = ({xpos, fill}) => {
 return {
   move: (pos) => {
     xpos = xpos + pos
     noStroke()
     ellipse( xpos,10,10,10)
   }
 }
} 

let bola;

function setup () {
  createCanvas(300,500)
  background(3)
  bola = createCircle(circleOpts)
}

function draw () {
 bola.move(10)
}

Ответы [ 2 ]

0 голосов
/ 11 ноября 2018

Это очень широкий вопрос, но я дам вам свой совет. Начните смотреть на setup -

function setup () {
  createCanvas(300,500) // where is the canvas created?

  background(3)         // background(3)? 3?
                        // does this set the background? on what?

  bola = createCircle(circleOpts) // this is better
                                  // createCircle accepts an input and it returns a value
                                  // don't forget: const bola = ...

}

В общем, вы захотите разработать функции, которые принимают входные и выходные данные. Давайте даже подумаем о типах входов и выходов, которые они могут иметь

createCircle : (x: Int, y: Int, r: Int) -> Element

moveCircle : (Element, x: Int, y: Int) -> Element

createCanvas : (h: Int, w: Int, bg: Int, elements: [Element]) -> Canvas

setBackground : (c: Canvas, bg: Int) -> Canvas

draw: (c: Canvas, e: Element) -> Canvas

Давайте ответим на ваш вопрос здесь. Мы реализуем createCircle, который создает простой объект. moveCircle примет Круг как вход, но не изменит его -

const createCircle = (x, y, r) =>
  ({ Element: "circle", x, y, r })

const moveCircle = (circle, x, y) =>
  createCircle       // <-- moveCircle creates a new circle
    ( circle.x + x   // using the circle's x value added to the new x
    , circle.y + y   // and the circle's y value added to the new y
    , circle.r       // the circle's radius, r, is unchanged
    )
    
const circle =
  createCircle (0, 0, 2)
  
const movedCircle =
  moveCircle (circle, -3, 3) // <-- circle is NOT mutated
  
console.log
  ( circle         // { Element: "circle", x: 0, y: 0, r: 2 }
  , movedCircle    // { Element: "circle", x: -3, y: 3, r: 2 }
  , circle         // { Element: "circle", x: 0, y: 0, r: 2 }
  )

Мы продолжим реализацию большей части вашей программы. Каждая функция имеет разумные параметры и производит вывод

const createCanvas = (h = 100, w = 100, bg = 0, elements = []) =>
  ({ h, w, bg, elements })

const setBackground = (canvas, bg) =>
  createCanvas          // <-- similar technique as moveCircle
    ( canvas.h          // we create a new canvas using the canvas's height
    , canvas.w          // and the canvas's width
    , bg                // setting the new bg here
    , canvas.elements   // and keeping the existing canvas's elements
    )

const draw = (canvas, element) =>
  createCanvas          // <-- same technique as above
    ( canvas.h          // ...
    , canvas.w          // ...
    , canvas.bg         // same canvas bg this time
    , append            // append the new element to
        ( canvas.elements  // the canvas's elements
        , element
        )
    )

const append = (xs, x) =>
  xs .concat ([ x ])    // <-- immutability everywhere (ie, no [].push)

Наконец, я представлю render. Это наш побочный эффект, который берет Canvas и выводит его на экран. Отличительной особенностью побочных (нечистых) функций является то, что они не имеют возвращаемого значения (null, undefined или void ) -

render: (c: Canvas) -> Void

const render = (canvas) =>
{ console.log (`creating ${canvas.w}x${canvas.h} with bg:${canvas.bg}`)
  for (const elem of canvas.elements)
    console.log (`rendering`, elem)
}

Это просто «рендерит» Canvas на консоль, но эффект тот же. Пишите в stdout, файл, отправляйте его по сети, это не имеет значения; Важно то, что мы держали все в чистоте до этой точки.

Вот как может выглядеть программа, использующая наши функции выше -

const main = () =>
{ const canvas =
    setBackground               // set background
      ( createCanvas (300, 500) // on a 300x500 canvas
      , 3                       // to bg:3 (yellow?)
      )

  const circle =
    createCircle (0, 0, 2)

  render (draw (canvas, circle)) // <-- render is an efffect
  // creating 500x300 with bg:3
  // rendering { Element: "circle", x: 0, y: 0, r: 2 }

  const movedCircle =
    moveCircle (circle, -3, 3)

  render (draw (canvas, movedCircle)) // <-- effect
  // creating 500x300 with bg:3
  // rendering { Element: "circle", x: -3, y: 3, r: 2 }
}

Вы можете проверить результаты в вашем собственном браузере ниже

const createCanvas = (h = 100, w = 100, bg = 0, elements = []) =>
  ({ h, w, bg, elements })

const setBackground = (canvas, bg) =>
  createCanvas
    ( canvas.h
    , canvas.w
    , bg
    , canvas.elements
    )

const createCircle = (x, y, r) =>
  ({ Element: "circle", x, y, r })
  
const moveCircle = (circle, x, y) =>
  createCircle
    ( circle.x + x
    , circle.y + y
    , circle.r
    )

const append = (xs, x) =>
  xs .concat ([ x ])

const draw = (canvas, element) =>
  createCanvas
    ( canvas.h
    , canvas.w
    , canvas.bg
    , append
        ( canvas.elements
        , element
        )
    )

const render = (canvas) =>
{ console.log (`creating ${canvas.w}x${canvas.h} with bg:${canvas.bg}`)
  for (const elem of canvas.elements)
    console.log (`rendering`, elem)
}
  
const main = () =>
{ const canvas =
    setBackground
      ( createCanvas (300, 500)
      , 3
      )

  const circle =
    createCircle (0, 0, 2)

  render (draw (canvas, circle))
  // creating 500x300 with bg:3
  // rendering { Element: "circle", x: 0, y: 0, r: 2 }
  
  const movedCircle =
    moveCircle (circle, -3, 3)
  
  render (draw (canvas, movedCircle))
  // creating 500x300 with bg:3
  // rendering { Element: "circle", x: -3, y: 3, r: 2 }
}

main ()

Я хочу кратко вернуться к createCanvas и изменить его с -

const createCanvas = (h = 100, w = 100, bg = 0, elements = []) =>
  ({ h, w, bg, elements })

до -

const createCanvas = (h = 100, w = 100, bg = 0, elements = []) =>
  ({ Element: "canvas", h, w, bg, elements })

Это позволит холстам состоять из других холстов. Разработка составных типов данных является краеугольным камнем функционального программирования -

const canvas =
  createCanvas
    ( 300
    , 500
    , 0
    , [ shape1, shape2, shape3 ]  // <-- containing a few elements ...
    )

const quadCanvas = (canvas) =>
  createCanvas
    ( canvas.h * 2
    , canvas.w * 2
    , canvas.bg
    , [ canvas, canvas, canvas, canvas ] // <-- rough example
    )

// 1 canvas       [%*$]

// quadCanvas     [%*$][%*$]
// (2x2)          [%*$][%*$]

Тогда наша функция render может расшириться до чего-то более похожего на -

const render = (canvas) =>
{ console.log (`creating ${canvas.w}x${canvas.h} with bg:${canvas.bg}`)
  for (const elem of canvas.elements)
    switch (elem.Element) {
      case 'circle': renderCirlce (elem) // <-- break complex tasks into smaller ones
      case 'line': renderLine (elem)

      case ...                           // <-- room for as many cases as you need

      case 'canvas': render (elem)       // <-- recursion! functional!

      default: throw Error (`cannot render unknown Element type: ${elem.Element}`)
        // <-- you must handle every scenario in your program
    }
}

const renderCircle = (circle) =>
{ ... }

const renderLine = (circle) =>
{ ... }

.... // <-- define element-specific renderers

Надеемся, что это заставит ваши ноги промокнуть и даст вам более четкое представление о том, как функционально думать о данных и процессах.

0 голосов
/ 11 ноября 2018

В этом случае все в порядке.

В вашем случае вы используете одну переменную для представления одного значения. Поскольку это значение меняется со временем, вполне логично, что переменная будет меняться со временем.

Как правило, я очень скептически отношусь к заявлениям о том, что XYZ всегда хорошая идея или всегда плохая идея. Все зависит от контекста.

...