如何在创建一个递增的值(如头寸)时消除可变性?
How to remove mutability when creating an increasing value such as a position?
我想在我的代码中使用更实用的方法,我有一个变量 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)
}
在这种情况下绝对没问题。
在您的例子中,您使用单个变量来表示单个值。由于此值随时间变化,因此变量会随时间变化是完全合理的。
一般来说,我非常怀疑 XYZ 总是 是个好主意或 总是 是个坏主意的说法。一切都取决于上下文。
这是一个非常广泛的问题,但我会给你我的建议。开始看 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 = ...
}
通常,您需要设计接受输入和 return 输出的函数。让我们甚至考虑一下它们可能具有的 类型 输入和输出
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
将接受 Circle 作为输入,但不会对其进行变异 -
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 并将其呈现到屏幕上。副作用(不纯)函数的一个标志性特征是它们没有 return 值(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)
}
这只是"renders"和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
希望这能让您有所了解,让您更清楚地了解如何以实用的方式思考数据和流程。
我想在我的代码中使用更实用的方法,我有一个变量 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)
}
在这种情况下绝对没问题。
在您的例子中,您使用单个变量来表示单个值。由于此值随时间变化,因此变量会随时间变化是完全合理的。
一般来说,我非常怀疑 XYZ 总是 是个好主意或 总是 是个坏主意的说法。一切都取决于上下文。
这是一个非常广泛的问题,但我会给你我的建议。开始看 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 = ...
}
通常,您需要设计接受输入和 return 输出的函数。让我们甚至考虑一下它们可能具有的 类型 输入和输出
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
将接受 Circle 作为输入,但不会对其进行变异 -
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 并将其呈现到屏幕上。副作用(不纯)函数的一个标志性特征是它们没有 return 值(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)
}
这只是"renders"和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
希望这能让您有所了解,让您更清楚地了解如何以实用的方式思考数据和流程。