Open Side Menu Go to the Top
Register
Best Practices of Code Organization Best Practices of Code Organization

06-12-2017 , 05:44 PM
Quote:
Originally Posted by daveT
And seeing that gaming_mouse popped in, I'll point out that he and I have a different interpretation of state and statelessness. I don't consider syntax a distinct feature of stateless programming and focus more on guarantees at a function level.

Not saying that one idea is more correct or incorrect, but my main idea is preventing accidental global mutation.
my example was perhaps not clear, but i don't think we should give aaron or anyone else the impression that the concept of statelessness is ill-defined or subjective. there's enough of that in CS without adding to it the concepts which are clear and have well-defined mathematical foundations. an answer less vulnerable to confusion might have been: "stateless programming is what you do in haskell"

but to salvage my example, it's not merely a matter of syntax. you have an array of data "arr". you have a function "sum" which operates on two arguments. you also have a function reduce which takes a function as an argument, and returns a new function -- that new function can now operate on arrays, returning their sum. you apply it to your actual array, "arr", and it returns the sum, leaving the original array untouched.

compare that to the original version, in which a variable "sum" is continually mutated as we pass through the loop, arriving at its final value.

Quote:
Originally Posted by Aaron W.
Thanks for that. It's true that I don't even know what I don't know.



Given that this is a first semester course for non-majors, I'm trying not to overwhelm them. Is there a good "framework" or something that I can give them a 10,000 foot overview of the different types of ideas that's sufficiently accurate without giving them too much information or muddling everything together?
this is a great question. i can't think of anything that perfectly fits the bill offhand, so i'll try to get back to you on that.



Quote:
If you go down far enough, isn't everything just about 0s and 1s and either changing the bits or using the bits to jump to another place in memory? And am I correct that the 0s and 1s are kind of what you mean by "stateful" and "changing/jumping" is what you mean by procedural?
it is. hence the whole concept doesn't make sense at the final implementation level. most obviously, higher-level, stateless languages like haskell are implemented in lower level languages like C, which are procedural. i find it helpful to think about "statelessness" as a constraint on the way you, as a human being, think about problems. and you often hear people saying that learning functional programming is learning a new way of thinking. this is a bit dramatic, but also true in a sense.

"functions," too, ultimately get compiled down to machine code, and instructions changing bits somewhere. but they are obviously useful to us as a way of organizing our code (and our thoughts). i like to consider every higher level concept as an extension of that idea. you can think about your code being executed by a magic genie, rather than a computer -- the underlying implementation is irrelevant to the question, "what concepts make it easier for human beings to think about and solve problems?"
Best Practices of Code Organization Quote
06-12-2017 , 06:24 PM
Quote:
Originally Posted by just_grindin
2. Code should avoid state as much as possible...
Quote:
Originally Posted by gaming_mouse
but to salvage my example, it's not merely a matter of syntax. you have an array of data "arr". you have a function "sum" which operates on two arguments. you also have a function reduce which takes a function as an argument, and returns a new function -- that new function can now operate on arrays, returning their sum. you apply it to your actual array, "arr", and it returns the sum, leaving the original array untouched.

compare that to the original version, in which a variable "sum" is continually mutated as we pass through the loop, arriving at its final value.
Okay...

So my impression of the advice is that I should avoid the creation of unnecessary variables and that it's better to avoid making things into variables whenever possible.
Best Practices of Code Organization Quote
06-12-2017 , 06:31 PM
Quote:
Originally Posted by Aaron W.
Okay...

So my impression of the advice is that I should avoid the creation of unnecessary variables and that it's better to avoid making things into variables whenever possible.
that wouldn't be accurate. naming is an orthogonal issue to mutation. it's just that, once named and assigned, you will never change the value that the name refers to.
Best Practices of Code Organization Quote
06-12-2017 , 07:29 PM
Quote:
Originally Posted by gaming_mouse
that wouldn't be accurate. naming is an orthogonal issue to mutation. it's just that, once named and assigned, you will never change the value that the name refers to.
What would that mean for the turtle racing thing? I have variables P1x through P4x that represent the x-coordinate of the turtle. Since the turtle is supposed to move, these values would be changing.

Unless "change the value that the name refers to" has more to do with the "what the name refers to" (that this is always referring to the x-coordinate and not some sort of intermediary thing) and not the "change the value" part?
Best Practices of Code Organization Quote
06-12-2017 , 09:31 PM
Quote:
Originally Posted by Aaron W.
What would that mean for the turtle racing thing? I have variables P1x through P4x that represent the x-coordinate of the turtle. Since the turtle is supposed to move, these values would be changing.

Unless "change the value that the name refers to" has more to do with the "what the name refers to" (that this is always referring to the x-coordinate and not some sort of intermediary thing) and not the "change the value" part?
this is a bigger question than it might appear. ofc, any meaningful program has state, somewhere -- the functional approach is to isolate that state, and any mutations of it, to one place. the rest of the program (read: all the complex logic) can then be written as pure functions.

in haskell this isolation is achieved with monads. in a modern js react app, written in a functional style, this is achieved with libraries like redux. functional reactive programming does it with streams. all of these are meaty topics. just google them if you're interested in learning more.

and i'd reiterate: i don't think the turtle thing should be rewritten in any of these styles. it _could_ be, it might be instructive, but the procedural style in which it's currently written suits it just fine, and its simple enough that anything more might rightly be considered over-engineering.
Best Practices of Code Organization Quote
06-12-2017 , 09:44 PM
I'm sorry I brought this topic up and derailed Aaron's thread but I'm certainly not sorry about the discussion that has come from the derail.

Sent from my SM-G900R4 using Tapatalk
Best Practices of Code Organization Quote
06-12-2017 , 10:06 PM
Quote:
Originally Posted by just_grindin
I'm sorry I brought this topic up and derailed Aaron's thread but I'm certainly not sorry about the discussion that has come from the derail.
I'm not sorry at all.
Best Practices of Code Organization Quote
06-13-2017 , 07:05 PM
Quote:
Originally Posted by gaming_mouse
this is a bigger question than it might appear. ofc, any meaningful program has state, somewhere -- the functional approach is to isolate that state, and any mutations of it, to one place. the rest of the program (read: all the complex logic) can then be written as pure functions.
I'm still thinking about this because it's the first time I've heard of this concept (at least, explicitly) and I'm still letting it roll around in my head. Overengineered as it may be, here's what I'm thinking.

The coordinates of the turtle can be obtained from the turtle directly:
Code:
turtle.location()
The x coordinate is the first element in the 2-tuple that it spits out.

It seems that it would be possible to rewrite the codes that advance the turtles as something like
Code:
turtle.setx( turtle.location()[0] + random.randint(3,10) )
And this could be turned into some sort of function (syntax might be a little off... I've never actually tried to pass an entire turtle to a function before, and I'm not on a computer that can test this right now).

Code:
def advance_turtle(turtle):
    turtle.setx( turtle.location()[0] + random.randint(3,10) )
    return ???

advance_turtle(P1)
advance_turtle(P2)
advance_turtle(P3)
advance_turtle(P4)
Is something like this the right type of idea in terms of getting the function "to one place"?
Best Practices of Code Organization Quote
06-13-2017 , 08:47 PM
each turtle is still being mutated there. as a hint, typically when you make object style code functional, the setters will return copies of the objects with the new data, rather than mutating the existing ones.

can you paste in the implementation of turtle? also just an english explanation of what the code does to save me time. it appears the turtles are racing, but just a bit more specifically. then i could probably quickly write a functional version.
Best Practices of Code Organization Quote
06-13-2017 , 08:49 PM
A very simple example of mutation:

Code:
>>> my_string = "hello"
>>> my_string += " world"
>>> my_string
'hello world'
immutabilty (no variables are being changed):

Code:
>>> my_string = "hello"
>>> my_string2 = my_string + " world"
>>> my_string
'hello'
>>> my_string2
'hello world'
Likewise, there is no concept of getter / setter in an immutable system. The position of turtle is initialized at the origin and the position of the turtle is always the value of the origin (as an example).

If you want to "advance" the turtle, you can set values that represent the movement of turtle relative to the origin. This is different than moving the actual turtle and storing it's position.

Mutation:

Code:
>>> for i in range(10):
...     turle.position(random)
This is destructive, since there is no historical record of where the turtle was. You can't record that position post hoc because that requires mutating variables inside of your class.

immutable:

Code:
>>> movement = [random * 10]
>>> for i in movement:
...     move_turtle(i)
This is presumably immutable because each call to the movement function does not depend on the position of the turtle, but rather expresses a path that the turtle is taking. The values inside of movement never changes. The value of the turtle position never changes.

Of course, the i in the for loop is mutating, so you would use more functional constructs like map:

Code:
map(move_turtle, movement)
The litmus test is:

Code:
>>> move_turtle(1)
always returns the same value, regardless how many times you call the function, and regardless what numbers you used before each successive call to "move_turtle(1)". Immutability guarantees the same output each and every time, which is why there no concept of getting / setting variables associated with a turtle object.
Best Practices of Code Organization Quote
06-13-2017 , 08:53 PM
So this turtle stuff is very similar to something I do a lot of, which is programming for CNC machines. I have a library that I use to generate gcode - gcode is a lot like your turtle stuff. The basic commands are basically like "go to X, Y, Z at this speed"

The way I do a goto is that I have a state object that has the current data for system - what tool we have, what coords we're at, what speed we're set at, etc. Whenever I do something that alters the state, I return a *new* version of this object (with the new params) and push the old version onto a list.

Keeping it in a list is optional - I do it because there are times when I want to inspect that list, or walk something backward, etc.

I sort of *think* that's what gaming_mouse is getting at but I'm not sure.

The benefit of this is, if something is holding onto the machine state, and then something else wants to change the state, the "held" version stays the same. This simplifies a lot of things.
Best Practices of Code Organization Quote
06-13-2017 , 09:11 PM
Quote:
Originally Posted by RustyBrooks
Actually, you're right. It's also been a long time since I programmed pascal - basically since I was in high school, uh, 22 years ago
First year uni for me - 1983.
Best Practices of Code Organization Quote
06-13-2017 , 09:13 PM
ur old
Best Practices of Code Organization Quote
06-13-2017 , 09:33 PM
Quote:
Originally Posted by gaming_mouse
can you paste in the implementation of turtle? also just an english explanation of what the code does to save me time. it appears the turtles are racing, but just a bit more specifically. then i could probably quickly write a functional version.
I've simplified the code and put some comments to point to what's happening.

Code:
import random
import turtle
import time

## Initialize the screen and drawing turtle

screen = turtle.Screen()

track = turtle.Turtle()
track.hideturtle()
track.pensize(3)
track.speed(0)

## Draw the starting line

track.penup()
track.goto(-250,-50)
track.pendown()
track.goto(-250,50)
track.penup()

## Draw the finish line

track.penup()
track.goto(250,-50)
track.pendown()
track.goto(250,50)
track.penup()

## Initialize Racing Turtles

P1 = turtle.Turtle()
P1.penup()
P1.shape('turtle')
P1.color('red')
P1.speed(0)
P1.goto(-268,25)

P2 = turtle.Turtle()
P2.penup()
P2.shape('turtle')
P2.color('blue')
P2.speed(0)
P2.goto(-268,-25)

P1x = -268
P2x = -268

## Dramatic pause before the race
track.goto(0,125)
time.sleep(random.randint(2,5))
track.write('GO!', font=("Arial", 14, "normal"), align='center')

## Loop of racing code

done = 0 
while done == 0:
    P1x = P1x + random.randint(3,10)
    P2x = P2x + random.randint(3,10)

    P1.setx(P1x)
    P2.setx(P2x)

    if P1x >= 232 or P2x >= 232:
        done = 1
Best Practices of Code Organization Quote
06-13-2017 , 09:37 PM
Quote:
Originally Posted by daveT
immutable:

Code:
>>> movement = [random * 10]
>>> for i in movement:
...     move_turtle(i)
In essence, this runs the race before it begins, and then the rest is merely displaying the results.
Best Practices of Code Organization Quote
06-13-2017 , 09:39 PM
Quote:
Originally Posted by RustyBrooks
So this turtle stuff is very similar to something I do a lot of, which is programming for CNC machines. I have a library that I use to generate gcode - gcode is a lot like your turtle stuff. The basic commands are basically like "go to X, Y, Z at this speed"
I've been working with a friend on some 3D printing projects that use gcode. I didn't know it was the same code they used in CNC machines.

Quote:
Originally Posted by RustyBrooks
The benefit of this is, if something is holding onto the machine state, and then something else wants to change the state, the "held" version stays the same. This simplifies a lot of things.
Everything made sense up until this part. What do you mean for something to "hold onto the machine state"?
Best Practices of Code Organization Quote
06-13-2017 , 10:08 PM
yeah gcode is used in 3d printers also. It's a terrible language, but we mostly don't use it for a language any more, not really. It's more of a data format now. I think it's actually turing complete though.

Regarding holding state... let's say I said

a = goto(x, y)
some_function()
print a.x, a.y

some_function may or may not call goto() itself. What's the print statement going to print?

If a is mutable, and goto() modifies it, then it'll print the values of x and y *after* whatever happens in some_function(). If it's not, then it'll print the values of x and y from *before* whatever happens.

If goto() modifies the state, then if we want to store the value of a at a specific period of time, we need to make a copy of it. Otherwise, other functions might change it.

Python has this problem with lists and dicts - you pass a list into a function, and if that function modifies the value you pass, then the value has changed in the calling frame also. This catches people up a lot.

Like say we have a function like

Code:
def save(data):
    data['x'] = data['x']+data['y']
    f = open('file.dat')
    print >> f, data

foo = {'x': 10, 'y': 20}
save(foo)
What's the value of "foo" after calling save()? It's {'x': 30, 'y': 20} because the argument that is passed to it is a mutable object. If the caller can't see the code it's running, it might not expect that the value it passes in might get mutated.
Best Practices of Code Organization Quote
06-13-2017 , 10:58 PM
Keeping it simple, and keeping in mind this is just for illustrating the concept, I'd break it down as follows. I'm using javascript, since I haven't used python in a while, and please note I have not tested or run this. With those caveats in mind...

You have turtles, which are a color and an x, y position. A turtle can advance forward by a certain amount (a pure function, no side effects or mutation):

Code:
function advanceTurtle(distance, turtle) {
  return {
    x: turtle.x + distance,
    y: turtle.y,
    color: turtle.color
  }
}
You also have a race, which consists of two turtles, and a finish line position. I'm going to use 0 as a start position for convenience, since the specific numbers in the original code are purely a view concern (ie, they matter only to the code which renders the race to the screen, so you can translate them at that point):

Code:
// example race object, at races start
const freshRace = {
  turtle1: {x: 0, y: 0, color: 'red'},
  turtle2: {x: 0, y: 0, color: 'blue'},
  finishLine: 100
}
Now we need a function to advance a race from one state to the next, and a function to tell you if the race is over:

Code:
function advanceRace(t1Inc, t2Inc, race) {
  return {
    turtle1: advanceTurtle(t1Inc, race.turtle1),
    turtle2: advanceTurtle(t2Inc, race.turtle2),
    finishLine: race.finishLine
  }
}

function isOver(race) {
  return race.turtle1 >= race.finishLine ||
    race.turtle2 >= race.finishLine
}
Everything is still pure up to now. Now we're going to contain our mutating state and other impurities (ie, the rng) in one place. Using streams would be a nice way to implement this, but I'm not going to do that because it'll just obsure the main of this post, which does not depend on streams or anything else. And just to show one of the benefits of this approach, we'll save every state of the race, so we'd get stuff like rewind and replay for free:

Code:
var raceStates = [freshRace]
// the last entry in the array of states
const currentRaceState = () => raceStates.slice(-1)
// impure (can easily make a pure version for testing)
const randomJump = () => 3 + Math.floor(Math.random() * 7)

while (!isOver(currentRaceState())) {
  // do something here with the current state, 
  // like render it to a view
  //
  // perhaps pause x milliseconds between jumps
  //
  const nextState = advanceRace(randomJump(), randomJump(), currentRaceState())
  raceStates.push(nextState) // only 1 line of mutation
}

Last edited by gaming_mouse; 06-13-2017 at 11:10 PM.
Best Practices of Code Organization Quote
06-14-2017 , 11:58 AM
Quote:
Originally Posted by Aaron W.
In essence, this runs the race before it begins, and then the rest is merely displaying the results.
It's what I did in that example, but that's not universally true, if that makes sense, and technically, the race can be ran at any time.

The concept of "turtle" is a constant. It will always be "turtle at origin."

The concept of "turtle movement" is constant, and not directly bound to the concept of "turtle."

It's the combination of the "turtle" and "turtle movement" facts that generate the fact that "turtle is, relative to the origin, here," but each step is held within a list data structure.

If you call the value of turtle, turtle is at origin. It never actually moves.

If you call movement, all the movements are in a record. That never changes.

By combining turtle and movement, you get the location of the turtle as if it had moved, but it's only a relative movement, not a change in any variable or state.

Simulations aren't the best application of immutability, however, it doesn't mean that 90% of your system shouldn't be immutable, etc.
Best Practices of Code Organization Quote
06-14-2017 , 02:00 PM
Thanks all for the explanations.
Best Practices of Code Organization Quote
06-23-2017 , 05:54 PM
Quote:
Originally Posted by RustyBrooks
yeah gcode is used in 3d printers also. It's a terrible language, but we mostly don't use it for a language any more, not really. It's more of a data format now. I think it's actually turing complete though.
I interviewed with a place that wrote middle ware for small/medium manufacturing plants, basically controlling industrial robots and CNC machines and a host of ancillary stuff that would tie your whole operation together. Would have been an interesting company to work for but the learning curve would have been an overhang. They supported basically every model of CNC machine and they all had quirks in their g-code so step one would have been "start reading owner's manual for CNC machines."
Best Practices of Code Organization Quote

      
m