How to remove mutability when creating an increasing value such as a position?
up vote
3
down vote
favorite
I am trying to use a more functional approach in my code, and I have a variable posx
which represents a position on the X axis.
It changes over time xpos = xpos + pos
:
const createCircle = (xpos, fill) =>
return
move: (pos) =>
xpos = xpos + pos
noStroke()
ellipse( xpos,10,10,10)
How can I not mutate the variable xpos
?
I've read that it is not good to mutate variables... so how would I do it then here?
Or is this ok in this case?
Rest of the code:
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)
javascript functional-programming processing p5.js
add a comment |
up vote
3
down vote
favorite
I am trying to use a more functional approach in my code, and I have a variable posx
which represents a position on the X axis.
It changes over time xpos = xpos + pos
:
const createCircle = (xpos, fill) =>
return
move: (pos) =>
xpos = xpos + pos
noStroke()
ellipse( xpos,10,10,10)
How can I not mutate the variable xpos
?
I've read that it is not good to mutate variables... so how would I do it then here?
Or is this ok in this case?
Rest of the code:
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)
javascript functional-programming processing p5.js
You're not actually mutating there - you're only reassigning (it's a primitive, after all - primitives are immutable)
– CertainPerformance
Nov 10 at 23:13
add a comment |
up vote
3
down vote
favorite
up vote
3
down vote
favorite
I am trying to use a more functional approach in my code, and I have a variable posx
which represents a position on the X axis.
It changes over time xpos = xpos + pos
:
const createCircle = (xpos, fill) =>
return
move: (pos) =>
xpos = xpos + pos
noStroke()
ellipse( xpos,10,10,10)
How can I not mutate the variable xpos
?
I've read that it is not good to mutate variables... so how would I do it then here?
Or is this ok in this case?
Rest of the code:
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)
javascript functional-programming processing p5.js
I am trying to use a more functional approach in my code, and I have a variable posx
which represents a position on the X axis.
It changes over time xpos = xpos + pos
:
const createCircle = (xpos, fill) =>
return
move: (pos) =>
xpos = xpos + pos
noStroke()
ellipse( xpos,10,10,10)
How can I not mutate the variable xpos
?
I've read that it is not good to mutate variables... so how would I do it then here?
Or is this ok in this case?
Rest of the code:
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)
javascript functional-programming processing p5.js
javascript functional-programming processing p5.js
edited Nov 11 at 0:51
Kevin Workman
32.9k53967
32.9k53967
asked Nov 10 at 23:11
Giorgio Martini
15318
15318
You're not actually mutating there - you're only reassigning (it's a primitive, after all - primitives are immutable)
– CertainPerformance
Nov 10 at 23:13
add a comment |
You're not actually mutating there - you're only reassigning (it's a primitive, after all - primitives are immutable)
– CertainPerformance
Nov 10 at 23:13
You're not actually mutating there - you're only reassigning (it's a primitive, after all - primitives are immutable)
– CertainPerformance
Nov 10 at 23:13
You're not actually mutating there - you're only reassigning (it's a primitive, after all - primitives are immutable)
– CertainPerformance
Nov 10 at 23:13
add a comment |
2 Answers
2
active
oldest
votes
up vote
1
down vote
accepted
It's absolutely fine in this case.
In your case, you're using a single variable to represent a single value. Since this value changes over time, it makes perfect sense that the variable would change over time.
Generally, I'd be very skeptical of claims that XYZ is always a good idea or always a bad idea. Everything depends on context.
What is "absolutely fine"? reassigningxpos
? callingnoStroke
without arguments and discarding its return value? Discarding the return value ofellipse
? Declaring variables withoutconst
orlet
orvar
?
– user633183
Nov 11 at 5:37
@user633183 Yes, all of that is absolutely fine in this context.
– Kevin Workman
Nov 11 at 6:23
@user633183 I'm not really sure what you mean? Flagging.
– Kevin Workman
Nov 11 at 7:14
add a comment |
up vote
-1
down vote
This is a really broad question, but I'll give you my advice. Start looking at 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 = ...
In general, you'll want to design functions that accept inputs and return outputs. Let's even think about the types of inputs and outputs they might have
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
Let's answer your question broadly here. We will implement createCircle
which constructs a simple object. moveCircle
will accept a Circle as input, but it will not mutate it -
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
)
We'll continue implementing more of your program. Each function has sensible parameters and produces an output
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)
Lastly I'll introduce render
. This is our side effect that takes a Canvas and renders it to the screen. A hallmark feature of side-effecting (impure) functions is that they have no return value (null
, undefined
, or void) -
render: (c: Canvas) -> Void
const render = (canvas) =>
console.log (`creating $canvas.wx$canvas.h with bg:$canvas.bg`)
for (const elem of canvas.elements)
console.log (`rendering`, elem)
This just "renders" the Canvas to the console, but the effect is the same. Write to stdout, a file, send it across the network, it doesn't matter; What matters is we kept everything pure up until this point.
Here's what a program might look like that uses our functions above -
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
You can verify the results in your own browser below
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.wx$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 ()
I want to briefly revisit createCanvas
and change it from -
const createCanvas = (h = 100, w = 100, bg = 0, elements = ) =>
( h, w, bg, elements )
to -
const createCanvas = (h = 100, w = 100, bg = 0, elements = ) =>
( Element: "canvas", h, w, bg, elements )
This would allow canvases to be composed of other canvases. Designing composable data types is a cornerstone of functional programming -
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) [%*$][%*$]
Then our render
function could expand to something more like -
const render = (canvas) =>
console.log (`creating $canvas.wx$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
Hopefully this gets your feet wet and gives you clearer picture of how to think about data and processes in a functional way.
1
Please note that a lot of this doesn't make a ton of sense in the P5.js context.
– Kevin Workman
Nov 11 at 7:22
Thank you very much for your great answer.. but its not very relevant in the context of p5js... and does not realy answer the question.
– Giorgio Martini
Nov 19 at 16:47
No worries. Functional programming is not relevant in the context of p5js. I never heard of it before answering this question. I hope you were able to learn something about functional programming anyway.
– user633183
Nov 20 at 3:57
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
It's absolutely fine in this case.
In your case, you're using a single variable to represent a single value. Since this value changes over time, it makes perfect sense that the variable would change over time.
Generally, I'd be very skeptical of claims that XYZ is always a good idea or always a bad idea. Everything depends on context.
What is "absolutely fine"? reassigningxpos
? callingnoStroke
without arguments and discarding its return value? Discarding the return value ofellipse
? Declaring variables withoutconst
orlet
orvar
?
– user633183
Nov 11 at 5:37
@user633183 Yes, all of that is absolutely fine in this context.
– Kevin Workman
Nov 11 at 6:23
@user633183 I'm not really sure what you mean? Flagging.
– Kevin Workman
Nov 11 at 7:14
add a comment |
up vote
1
down vote
accepted
It's absolutely fine in this case.
In your case, you're using a single variable to represent a single value. Since this value changes over time, it makes perfect sense that the variable would change over time.
Generally, I'd be very skeptical of claims that XYZ is always a good idea or always a bad idea. Everything depends on context.
What is "absolutely fine"? reassigningxpos
? callingnoStroke
without arguments and discarding its return value? Discarding the return value ofellipse
? Declaring variables withoutconst
orlet
orvar
?
– user633183
Nov 11 at 5:37
@user633183 Yes, all of that is absolutely fine in this context.
– Kevin Workman
Nov 11 at 6:23
@user633183 I'm not really sure what you mean? Flagging.
– Kevin Workman
Nov 11 at 7:14
add a comment |
up vote
1
down vote
accepted
up vote
1
down vote
accepted
It's absolutely fine in this case.
In your case, you're using a single variable to represent a single value. Since this value changes over time, it makes perfect sense that the variable would change over time.
Generally, I'd be very skeptical of claims that XYZ is always a good idea or always a bad idea. Everything depends on context.
It's absolutely fine in this case.
In your case, you're using a single variable to represent a single value. Since this value changes over time, it makes perfect sense that the variable would change over time.
Generally, I'd be very skeptical of claims that XYZ is always a good idea or always a bad idea. Everything depends on context.
edited Nov 10 at 23:51
answered Nov 10 at 23:22
Kevin Workman
32.9k53967
32.9k53967
What is "absolutely fine"? reassigningxpos
? callingnoStroke
without arguments and discarding its return value? Discarding the return value ofellipse
? Declaring variables withoutconst
orlet
orvar
?
– user633183
Nov 11 at 5:37
@user633183 Yes, all of that is absolutely fine in this context.
– Kevin Workman
Nov 11 at 6:23
@user633183 I'm not really sure what you mean? Flagging.
– Kevin Workman
Nov 11 at 7:14
add a comment |
What is "absolutely fine"? reassigningxpos
? callingnoStroke
without arguments and discarding its return value? Discarding the return value ofellipse
? Declaring variables withoutconst
orlet
orvar
?
– user633183
Nov 11 at 5:37
@user633183 Yes, all of that is absolutely fine in this context.
– Kevin Workman
Nov 11 at 6:23
@user633183 I'm not really sure what you mean? Flagging.
– Kevin Workman
Nov 11 at 7:14
What is "absolutely fine"? reassigning
xpos
? calling noStroke
without arguments and discarding its return value? Discarding the return value of ellipse
? Declaring variables without const
or let
or var
?– user633183
Nov 11 at 5:37
What is "absolutely fine"? reassigning
xpos
? calling noStroke
without arguments and discarding its return value? Discarding the return value of ellipse
? Declaring variables without const
or let
or var
?– user633183
Nov 11 at 5:37
@user633183 Yes, all of that is absolutely fine in this context.
– Kevin Workman
Nov 11 at 6:23
@user633183 Yes, all of that is absolutely fine in this context.
– Kevin Workman
Nov 11 at 6:23
@user633183 I'm not really sure what you mean? Flagging.
– Kevin Workman
Nov 11 at 7:14
@user633183 I'm not really sure what you mean? Flagging.
– Kevin Workman
Nov 11 at 7:14
add a comment |
up vote
-1
down vote
This is a really broad question, but I'll give you my advice. Start looking at 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 = ...
In general, you'll want to design functions that accept inputs and return outputs. Let's even think about the types of inputs and outputs they might have
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
Let's answer your question broadly here. We will implement createCircle
which constructs a simple object. moveCircle
will accept a Circle as input, but it will not mutate it -
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
)
We'll continue implementing more of your program. Each function has sensible parameters and produces an output
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)
Lastly I'll introduce render
. This is our side effect that takes a Canvas and renders it to the screen. A hallmark feature of side-effecting (impure) functions is that they have no return value (null
, undefined
, or void) -
render: (c: Canvas) -> Void
const render = (canvas) =>
console.log (`creating $canvas.wx$canvas.h with bg:$canvas.bg`)
for (const elem of canvas.elements)
console.log (`rendering`, elem)
This just "renders" the Canvas to the console, but the effect is the same. Write to stdout, a file, send it across the network, it doesn't matter; What matters is we kept everything pure up until this point.
Here's what a program might look like that uses our functions above -
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
You can verify the results in your own browser below
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.wx$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 ()
I want to briefly revisit createCanvas
and change it from -
const createCanvas = (h = 100, w = 100, bg = 0, elements = ) =>
( h, w, bg, elements )
to -
const createCanvas = (h = 100, w = 100, bg = 0, elements = ) =>
( Element: "canvas", h, w, bg, elements )
This would allow canvases to be composed of other canvases. Designing composable data types is a cornerstone of functional programming -
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) [%*$][%*$]
Then our render
function could expand to something more like -
const render = (canvas) =>
console.log (`creating $canvas.wx$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
Hopefully this gets your feet wet and gives you clearer picture of how to think about data and processes in a functional way.
1
Please note that a lot of this doesn't make a ton of sense in the P5.js context.
– Kevin Workman
Nov 11 at 7:22
Thank you very much for your great answer.. but its not very relevant in the context of p5js... and does not realy answer the question.
– Giorgio Martini
Nov 19 at 16:47
No worries. Functional programming is not relevant in the context of p5js. I never heard of it before answering this question. I hope you were able to learn something about functional programming anyway.
– user633183
Nov 20 at 3:57
add a comment |
up vote
-1
down vote
This is a really broad question, but I'll give you my advice. Start looking at 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 = ...
In general, you'll want to design functions that accept inputs and return outputs. Let's even think about the types of inputs and outputs they might have
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
Let's answer your question broadly here. We will implement createCircle
which constructs a simple object. moveCircle
will accept a Circle as input, but it will not mutate it -
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
)
We'll continue implementing more of your program. Each function has sensible parameters and produces an output
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)
Lastly I'll introduce render
. This is our side effect that takes a Canvas and renders it to the screen. A hallmark feature of side-effecting (impure) functions is that they have no return value (null
, undefined
, or void) -
render: (c: Canvas) -> Void
const render = (canvas) =>
console.log (`creating $canvas.wx$canvas.h with bg:$canvas.bg`)
for (const elem of canvas.elements)
console.log (`rendering`, elem)
This just "renders" the Canvas to the console, but the effect is the same. Write to stdout, a file, send it across the network, it doesn't matter; What matters is we kept everything pure up until this point.
Here's what a program might look like that uses our functions above -
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
You can verify the results in your own browser below
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.wx$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 ()
I want to briefly revisit createCanvas
and change it from -
const createCanvas = (h = 100, w = 100, bg = 0, elements = ) =>
( h, w, bg, elements )
to -
const createCanvas = (h = 100, w = 100, bg = 0, elements = ) =>
( Element: "canvas", h, w, bg, elements )
This would allow canvases to be composed of other canvases. Designing composable data types is a cornerstone of functional programming -
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) [%*$][%*$]
Then our render
function could expand to something more like -
const render = (canvas) =>
console.log (`creating $canvas.wx$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
Hopefully this gets your feet wet and gives you clearer picture of how to think about data and processes in a functional way.
1
Please note that a lot of this doesn't make a ton of sense in the P5.js context.
– Kevin Workman
Nov 11 at 7:22
Thank you very much for your great answer.. but its not very relevant in the context of p5js... and does not realy answer the question.
– Giorgio Martini
Nov 19 at 16:47
No worries. Functional programming is not relevant in the context of p5js. I never heard of it before answering this question. I hope you were able to learn something about functional programming anyway.
– user633183
Nov 20 at 3:57
add a comment |
up vote
-1
down vote
up vote
-1
down vote
This is a really broad question, but I'll give you my advice. Start looking at 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 = ...
In general, you'll want to design functions that accept inputs and return outputs. Let's even think about the types of inputs and outputs they might have
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
Let's answer your question broadly here. We will implement createCircle
which constructs a simple object. moveCircle
will accept a Circle as input, but it will not mutate it -
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
)
We'll continue implementing more of your program. Each function has sensible parameters and produces an output
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)
Lastly I'll introduce render
. This is our side effect that takes a Canvas and renders it to the screen. A hallmark feature of side-effecting (impure) functions is that they have no return value (null
, undefined
, or void) -
render: (c: Canvas) -> Void
const render = (canvas) =>
console.log (`creating $canvas.wx$canvas.h with bg:$canvas.bg`)
for (const elem of canvas.elements)
console.log (`rendering`, elem)
This just "renders" the Canvas to the console, but the effect is the same. Write to stdout, a file, send it across the network, it doesn't matter; What matters is we kept everything pure up until this point.
Here's what a program might look like that uses our functions above -
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
You can verify the results in your own browser below
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.wx$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 ()
I want to briefly revisit createCanvas
and change it from -
const createCanvas = (h = 100, w = 100, bg = 0, elements = ) =>
( h, w, bg, elements )
to -
const createCanvas = (h = 100, w = 100, bg = 0, elements = ) =>
( Element: "canvas", h, w, bg, elements )
This would allow canvases to be composed of other canvases. Designing composable data types is a cornerstone of functional programming -
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) [%*$][%*$]
Then our render
function could expand to something more like -
const render = (canvas) =>
console.log (`creating $canvas.wx$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
Hopefully this gets your feet wet and gives you clearer picture of how to think about data and processes in a functional way.
This is a really broad question, but I'll give you my advice. Start looking at 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 = ...
In general, you'll want to design functions that accept inputs and return outputs. Let's even think about the types of inputs and outputs they might have
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
Let's answer your question broadly here. We will implement createCircle
which constructs a simple object. moveCircle
will accept a Circle as input, but it will not mutate it -
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
)
We'll continue implementing more of your program. Each function has sensible parameters and produces an output
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)
Lastly I'll introduce render
. This is our side effect that takes a Canvas and renders it to the screen. A hallmark feature of side-effecting (impure) functions is that they have no return value (null
, undefined
, or void) -
render: (c: Canvas) -> Void
const render = (canvas) =>
console.log (`creating $canvas.wx$canvas.h with bg:$canvas.bg`)
for (const elem of canvas.elements)
console.log (`rendering`, elem)
This just "renders" the Canvas to the console, but the effect is the same. Write to stdout, a file, send it across the network, it doesn't matter; What matters is we kept everything pure up until this point.
Here's what a program might look like that uses our functions above -
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
You can verify the results in your own browser below
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.wx$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 ()
I want to briefly revisit createCanvas
and change it from -
const createCanvas = (h = 100, w = 100, bg = 0, elements = ) =>
( h, w, bg, elements )
to -
const createCanvas = (h = 100, w = 100, bg = 0, elements = ) =>
( Element: "canvas", h, w, bg, elements )
This would allow canvases to be composed of other canvases. Designing composable data types is a cornerstone of functional programming -
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) [%*$][%*$]
Then our render
function could expand to something more like -
const render = (canvas) =>
console.log (`creating $canvas.wx$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
Hopefully this gets your feet wet and gives you clearer picture of how to think about data and processes in a functional way.
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 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
( 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.wx$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 ()
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.wx$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 ()
edited Nov 11 at 7:00
answered Nov 11 at 5:35
user633183
66.7k21130172
66.7k21130172
1
Please note that a lot of this doesn't make a ton of sense in the P5.js context.
– Kevin Workman
Nov 11 at 7:22
Thank you very much for your great answer.. but its not very relevant in the context of p5js... and does not realy answer the question.
– Giorgio Martini
Nov 19 at 16:47
No worries. Functional programming is not relevant in the context of p5js. I never heard of it before answering this question. I hope you were able to learn something about functional programming anyway.
– user633183
Nov 20 at 3:57
add a comment |
1
Please note that a lot of this doesn't make a ton of sense in the P5.js context.
– Kevin Workman
Nov 11 at 7:22
Thank you very much for your great answer.. but its not very relevant in the context of p5js... and does not realy answer the question.
– Giorgio Martini
Nov 19 at 16:47
No worries. Functional programming is not relevant in the context of p5js. I never heard of it before answering this question. I hope you were able to learn something about functional programming anyway.
– user633183
Nov 20 at 3:57
1
1
Please note that a lot of this doesn't make a ton of sense in the P5.js context.
– Kevin Workman
Nov 11 at 7:22
Please note that a lot of this doesn't make a ton of sense in the P5.js context.
– Kevin Workman
Nov 11 at 7:22
Thank you very much for your great answer.. but its not very relevant in the context of p5js... and does not realy answer the question.
– Giorgio Martini
Nov 19 at 16:47
Thank you very much for your great answer.. but its not very relevant in the context of p5js... and does not realy answer the question.
– Giorgio Martini
Nov 19 at 16:47
No worries. Functional programming is not relevant in the context of p5js. I never heard of it before answering this question. I hope you were able to learn something about functional programming anyway.
– user633183
Nov 20 at 3:57
No worries. Functional programming is not relevant in the context of p5js. I never heard of it before answering this question. I hope you were able to learn something about functional programming anyway.
– user633183
Nov 20 at 3:57
add a comment |
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53244329%2fhow-to-remove-mutability-when-creating-an-increasing-value-such-as-a-position%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
You're not actually mutating there - you're only reassigning (it's a primitive, after all - primitives are immutable)
– CertainPerformance
Nov 10 at 23:13