Saturday, January 23, 2021

Dynamic Typing in Python

Python is an extremely powerful language, but with great power comes great capacity to really screw things up.  Overall, Python is an extremely well designed language, but there are a few aspects of it that get attacked a lot.  Using style for control flow is a big one, though some of us actually really like this feature.  Another common complaint is with Python's dynamic typing.  Dynamic typing allows programmers to really screw things up in ways that can be really hard to debug.  Supposedly, it is common for less experienced Python programmers to accidentally have variable naming collisions, and because Python has dynamic typing, there is no type safety system to warn the programmer (though, I should note that no one has ever presented me with any evidence that this is a common problem, and even when I was new to Python, I never encountered this problem in my own code, and when I taught Python to undergrad students none of them ever reported having problems or frustration with this).  Some opponents of dynamic typing even suggest that the ability to change variable types has no practical value and thus should not exist in any programming language.  As someone who has made extensive use of Python's dynamic typing, to great advantage, I would like to present some highly valuable applications of it that have saved me enormous amounts of time.

The one that always comes to mind for me, is the ability to dynamically change objects.  Wait, objects?  How do objects have anything to do with it?  Objects are (composite) variables.  They have a type, that is defined by their class.  Wait, so you can change the class of an object dynamically?  Not exactly, and this is the wrong way to think about it (in this context...).  Most programmers think of types in terms of primitives, which is why many opponents of dynamic typing see no value in it.  If you see objects as collections rather than variables with their own types (defined by the names and types of their contents), it's easy to miss this.  (In Python, functions are also variables, so objects in Python are literally types defined by the variables they contain and their names.  Classes are merely formal definitions of those types, but it's trivial to create undefined object types as well.)  What does it mean to change the type of a primitive?  It means you are storing a value of a different type in it.  If you have a variable containing a string, and you store an integer in it, the string is completely replaced.  Objects are not primitive types though.  They are composite types, that can contain both data and code (or, to be more precise, pointers to code, in nearly all implementations), and multiple elements of each.  While writing a primitive type to a variable containing an object will overwrite the object (or rather, the pointer to it), dynamic typing is more than just overwriting the existing data with data of a different type.  Changing the data type of a primitive, without overwriting it with new data doesn't make a lot of sense.  Changing the type of an object, without overwriting it, does make sense, so long as you don't think in terms of changing the class of the object to another fully defined class.

In Python, most objects (excluding built-in objects) can be dynamically changed, by adding methods and member variables.  Thus, if you have created an instance of a user defined class (aka, an object), and it needs to have an additional member variable and/or method, in Python it is easy to just add it.  Now, it might be tempting to think even this is useless, but in Python it is incredibly powerful.  It allows for macro-like ability in the language, and it is fairly well known that macros are what makes Lisp such a powerful language.  Python's dynamic typing isn't quite as powerful as Lisp macros, but it allows for many of the same kinds of advanced metaprogramming.  In addition, because it is also possible in Python to create empty objects, by instantiating objects that inherit only from the object() class (object() itself is built-in and thus its direct instances are not dynamic), it is also possible to build completely arbitrary objects at runtime.

One of my favorite uses of dynamic typing in Python is adding methods to existing objects, to give those objects additional abilities.  In the context of video games, this means I can add a "render()" method to existing objects, if I want.  Of course it is more practical to just include render() in the original class definition, which is what I actually do.  The practical value of this is much greater, when I am using 3rd party libraries with objects that I want to change, without changing those libraries.

Another handy use of dynamic typing in Python is to assign extra metadata to existing objects.  For example, say I need an identification system for a collection of objects.  I could easily add a universal identifier to all objects put into that collection.  If I need to find or verify a particular object by UID, I can easily do that now, without having to change the class definitions (which I may not even have access to) of all of the objects.  And, now my system can take arbitrary object types, tack on the UID, and use them, without having to know anything about the objects.  Since Python can have collections of different types of objects (which we will talk about in a moment), this makes certain kinds of dispatch systems really easy, and adding UIDs and other metadata to objects can facilitate very advanced security protocols far more easily than languages that don't have dynamic typing.  Sure, I could write a wrapper object, that holds an object being passed in, and contains all of the metadata, and in some cases, this would be the optimal approach, however, this is less efficient (more layers of objects), and it would be significantly harder to add to an existing system.  In addition, a special authentication wrapper could lead to increased coupling between the authorization model and other modules that would have to unwrap objects when accessing them.  If I need to add an authorization protocol to an existing system, or if I need a very cheap authorization protocol that takes minimal time to develop, taking advantage of Python's dynamic typing will easily allow it, with no risk of potentially harmful coupling between the security system and the other systems.

The most complex way I have ever applied Python's dynamic typing system was in automated web site QA testing with Selenium.  This is an especially complicated application, because each page has its own unique set of properties and actions.  Initially, I used a non-object oriented approach, but this proved problematic, as each test needs to be painstakingly created, entirely from scratch, often with complicated CSS selector strings to interact with all of the elements on the page.  Even with three other employees writing tests, we were only writing two or three tests a day, to test usage paths that would only take us a couple minutes each testing manually.  In addition, logging is problematic, as either each individual test needs a bunch of custom logging code, or a global logger only logs minimal data about test failures.  The company I worked for at the time had used PHP for Selenium testing a little before I was hired, and they had attempted to get around these problems by writing a framework to handle a lot of this, but it constantly needed modification, because everything was hardcoded.  Using Python, I wrote a semi-complicated program, using Python's metaprogramming capabilities to dynamically generate a custom object for each page, with methods for all of the actions on the page.  The initial iteration still required test developers to go through each page, adding actions and any input elements to a JSON file, that my program would generate the objects from, but the long term plan was for the program to scan the HTML, looking for interactive elements and their input fields, and generate the page objects from that.  The finished product also would have scraped the page for information to include in logs, when tests failed (the page title, error messages, and such).  This way, a test writer could go to a web site, get the first page object, and then navigate purely by calling action methods on page objects, and then using the returned page object (representing the new page), and when a test failed, the log would automatically include detailed error information, rather than just the URL, the assertion that failed, and whatever description the test writer put in the assertion.  If a dev got to a new page and didn't know what actions were available, Python's introspection capabilities could be used to see all of the actions associated with the page object, to see what options the page presented.  While I quit that job when they decided to migrate their automated testing to Java (making test development much slower and eliminating any metaprogramming capabilities), my program would have allowed amateur programmers (we hired a lot of interns in their first few semesters of CS undergrad studies) to write tests very quickly, without having to even know HTML or CSS, and without having to spend hours going through web pages to construct complicated CSS selectors.

The truth is, most Python programmers use dynamic typing quite often.  It is not typically used for changing data types dynamically though.  It is used in heterogenous lists.  Heterogenous lists (Python's default array/list/collection type) can hold objects of any type, in any combination.  This is really handy, because it means metadata can exist as type, rather than as an explicit part of a variable.  For example, say you are creating an event queue.  It needs to hold KeyboardEvent objects, MouseEvent objects, and a bunch of other event types.  In C or C++, you will have to make an Event type (object or struct) and then have the subtypes inherit (or use a union), and then you will need to hold metadata about what type each object is, within each object (or struct), and then you have to cast Events going into and out of the queue, and from there you need separate code for each type.  (The degree of run-on-ness of that sentence is a good indicator of the complexity of the work involved.)  In Java, type data is handled automatically, but you still have to inherit, cast, and handle each type separately.  In Python, I don't need to inherit from a common class (less coding and greater maintainability), and I don't strictly need to check type.  I can use separate code for each type if I want, but I can also just check if properties exist directly and act appropriately.  For example, if I get an event, I might check if it has a "keydown" property, and if it does, I can check which key it was.  I don't need to know that the object is a KeyboardEvent object, and I don't need to cast objects coming out of the queue to their original type.  And in fact, say I want to use the event system to control an AI opponent.  I can make an AIEvent object with keyup, keydown, and whatever I am calling mouse event parameters, and toss that onto the queue, and so long as I have some way for clients of the queue to tell what events belong to them, I can use the same code I am using for the playable character for AI units!  Now, my AI units can take only AIEvent objects off the queue (yeah, they have to check type for that, and that's fine), and then they can feed the event into the same unit control code the playable characters use.  If this use sounds similar to Java's interfaces, it can be used similarly, to add similar capabilities to a collection of otherwise completely different objects.  So sure, this can be done with other object oriented languages (indeed, this is a common game programming design pattern), but with Python, I can do it much faster, in far less code, because I don't need a special queue, type casting, or as much type checking.  For comparison, SDL, a video game programming library, uses structs and unions, with type metadata variables (that the user must check), to achieve similar behavior.  (C unions are really powerful, and they can be used to create fully dynamic types in C, but with more hazards than Python, because it's easier to overflow a memory location using unions.)

The fact is, dynamic typing is far more than just being able to change the type of a variable at runtime.  It's being able to dynamically redefine objects.  It's being able to store elements with different data types in the same list construct.  It's being able to use objects of different types with related functionality with the same code, without having to do any complicated type casting.  Sure, dynamic typing increases the potential for bugs, but it also decreases potential for bugs, by making code simpler and shorter.  The hazards of dynamic typing are generally far outweighed by the benefits of increasing simplicity and decreasing code volume.

Wednesday, January 20, 2021

Complex Evens and Odds

 I have sometimes found myself enjoying experimental math with questionable practicality.  For example, fractional bases can be fun to play with, and negative bases can be really confusing.  The practical value of fractional bases probably does not exist, and negative bases may have some practical uses, but the complexity is high enough that there probably aren't many practical uses for them.  In writing the previous article on the terms "even" and "odd", I ended up being forced to consider the implications of those terms in complex numbers, and I have discovered some interesting things.  So, I am going to write about it!


Initially, I assumed even and odd cannot apply to complex numbers, however, it turns out I was wrong, and I even managed to prove my wrongness with an example.  There are a few problems that make this hard to understand.  First, complex numbers aren't just two independent numbers.  A complex number is essentially a 2D number, represented as a real and an imaginary component.  It's still one number though.  Now, the magnitude of a complex number isn't just its component added together.  It's more complicated than that.  It's actually the length hypotenuse of the right triangle that would be made, if the other two sides were the length of the two components.  This means that a complex number with magnitude 2 might be represented as sqrt(2) + sqrt(2)i.  The components don't necessarily have to be integers for the actual magnitude to be an integer value, and that really complicates things.  Another problem is that even and odd only make sense in the context of discrete math, thus we can't call sqrt(2) + sqrt(2)i even, because its components are not integers and thus don't fall into a domain governed by discrete math.  This is severely limiting, because it means that complex numbers that fall into the integer domain must have integer components and must have integer magnitude.  That excludes a ton of complex numbers that have integer components, because most don't have integer magnitude.

I am not sure how useful this is.  I think it clearly falls into the realm of experimental math, with questionable practicality, but I find this interesting, and if it even might have practical value, it is worth putting some time into exploring.  The rest of this will be some examples and exploration, to get a better feel for what we are looking at, and to see if anything interesting arises from it.


So first, 1 + 1i doesn't have an integer magnitude, so it isn't a valid complex integer.  Now, on a graph, complex integers can only exist on grid vertices.  This will help narrow down the possible options in a way that can be understood visually.  Since we know 1 + 1i isn't an integer (it's magnitude is 1.41), we know that not all vertices are complex integers.  I am thinking we could take a numbered grid, and then put circles on it at integer distances from the origin, and anywhere a circle precisely intersects a grid vertex, there is a complex integer.  We would, of course, want to verify any integers we find, as we may see an apparent intersection that is merely extremely close and only appears to intersect due to resolution limitations.

There are a few intersection points I know off the top of my head.  For example, the last article used 3 + 4i as an example.  This is the common Pythagoras theorem example, with a magnitude of 5.  We can also swap components, for 4 + 3i = 5, and we can swap signs as well.  This means we have rotational symmetry at 90 degree (quadrant) intervals, and within each quadrant we have reflective symmetry across the 45 degree quadrant bisector.  This is similar to algebraic rules in real math, like the Commutative property, except that because magnitudes are always positive, negative and positive values can be switched without changing the magnitude.  (This does bring up the question of if and how angle plays into this.  Not sure I want to deal with that right now, and I suspect that adding angles will break the whole integer thing, because I seriously doubt any/many complex integers will land on integer angles.  That said, the scale for angles is arbitrary, so "integer" really doesn't apply in any real way...unless maybe this exercise reveals a discrete scale of angles that has thus far remained undiscovered...  Maybe I need to print off this graph with circles and draw lines from complex integers to the origin, and see if that yields any interesting patterns...)  Any integer multiple of either of these is also going to have an integer magnitude.  And that leads to the realization that we are actually looking at vector math here.  We can also represent complex numbers as vectors, <3, 4> or coordinates, (3, 4), and realizing this, it is now obvious to me that the complex domain uses vector algebra for its operations, and this makes me wonder how vector algebra would work in the complex integer domain.  Honestly, I am beginning to think I may have just gotten myself way deeper than I did with negative bases.  I am hoping this isn't more complex than non-reciprocal fractional bases...  (Reciprocal bases, ie., fractional bases where the numerator is always 1, are trivially easy.  The one time I tried a fractional base with a numerator higher than 1, however, was a disaster.  That's one of the most complicated mathematical things I have tried and failed to do.  And I should note, I got an A in multivariable calculus.)

So, I think I am going to make the graph with circles at integer distances from the origin and see if I can find more complex integers.  From there, I can look for patterns.  I am sure I will find some, because that's just what happens with math, but the question now is whether I will find anything significant or not!

I guess I know where I am going from here.  I have convinced myself, through this line of thought, that this might have some practical value after all.  It might only serve to move math theory forward, but that is practical value, as most elements of math theory seem to eventually lead to something of significant value being discovered or created.  I am seeing a lot of potential in discovering interesting and perhaps undiscovered patterns from this, so maybe...

Evens and Odds

Many years ago, a friend of mine questioned the common practice of always rounding halves up.  That lead me to write this article, just a few minutes ago.  It also led me to question elements of math theory and education in general.  That let me down a line of reasoning that motivating me to write this article, and I ended up writing that one first as an example.  (If that logic is confusing, it's because it is.  Basically, I was going to precede the following with a paragraph about the question my friend raised, but by the time I finished, I found I had written a whole article...  Yeah, that happens to me sometimes.)  Anyhow, on to the topic at hand.


In elementary school math, at least in the U.S., there is a point where a lot of emphasis is put on classifying numbers as even or odd.  We are taught, implicitly if not explicitly, that oddness and evenness are fundamental properties of numbers, and that telling the difference is a critical math skill.  Rather than exploring these ideas, let's just cut to the chase: This is all wrong.  Oddness and evenness are not fundamental properties of numbers, and there is very limited value in being able to classify numbers as even or odd on sight.

The first problem is that the vast majority of numbers are neither even nor odd.  I hear you saying, "But half of all numbers are even, aren't they?"  Nope, not even close.  In fact, so few numbers are even, that one might reasonably claim that evenness doesn't exist, statistically.  How can I say this, when you can just count by twos indefinitely or even provide a mathematical proof that there are an infinite number of even numbers?  Consider, how many numbers are there between 0 and 1?  The answer is an uncountably infinite number.  0.1, 0.01, 0.001, 0.2, and so on, and not a single one is even.  In addition, they are not odd either!  One the other hand, even numbers (and odd numbers) are only countably infinite, and for all practical intent, countably infinite divided by uncountably infinite is zero, thus the percentage of all numbers that are even or odd is 0%.  It's almost (but not quite) like even and odd numbers don't even exist.

Now, if we add some qualifiers, we can make even and odd numbers relevant.  Instead of saying that evenness and oddness are fundamental properties of numbers, let's instead say they are fundamental properties of integers, aka whole numbers.  This is actually true, and every whole number is either even or odd.  Well, that may not be precisely true...  Let's further qualify that, every whole real number is either even or odd.  (Imaginary numbers...  There are a few ways you might try to qualify complex numbers as even or odd, but they are unintuitive.  Purely imaginary integers, with no real component, could be even or odd the same way real integers are.  For example, 2i is even.  Complex numbers, however, really break when it comes to even and odd.  For example, technically sqrt(2) + sqrt(2)i is even, because it's magnitude is 2, and thus, when divided by 2, its magnitude is 1.  Also, 6 + 8i has magnitude 10, and dividing it by 2 is 3 + 4i, with magnitude 5, and if those look familiar, it's because they are the common example for Pythagoras Theorem.)  So, if we limit our set of possible values to real integers, even and odd are relevant, and every possible value is either even or odd.  Now it is a fundamental property.

We can make even and odd relevant, by limiting our set of values, but are these designations useful?  We first need to define "even" and "odd", before we can really determine their value.  In schools, we are taught that numbers that divide "evenly" by 2 are even, and those that don't are odd.  What does this mean though?  In real numbers, I can divide say, 3 by 2 and get 1.5.  Where's the problem?  3 seems to divide by 2 evenly.  Again, we have to limit our domain to integers for this to make sense.  At the deepest level, what "even" means in this context is that when we divide a number by 2, all groups this creates have an equal (or even) number of elements.  So, if I divide 3 by 2, I get a group of 2 and a group of 1, thus 3 isn't even, but if I divide 4 by 2 I get two groups of 2, which are of equal size, this 4 is even.  We can define evenness more mathematically though, using discrete math concepts.  Discrete math is merely math using only whole numbers, aka "discrete" values rather than continuous values (real numbers).  Discrete math has different mathematical operations than continuous math.  The prime example is division, where in continuous math, you just split numbers in smaller parts, for example, 3 / 2 = 1.5, but in discrete math, division gives two outputs.  One is the number of even groups the numerator gets split into, and the other is the size of the remaining uneven group, otherwise known as the remainder.  Thus, in discrete math, 3 / 2 = 1R1, that is, 1 with a remainder of 1.  Discrete math also has an additional operation that is a partial division, that yields only the remainder, which is called the "modulus" operation.  In programming, the modulus operation is often represented with the percent sign, thus 3 % 2 = 1.  Now we have a foundation for defining "even" and "odd".  An even number is a number where the modulus is 0, when it is divided by 2, or, x is even if and only if x % 2 = 0.  A number x is odd if and only if x % 2 = 1.  So even and odd are actually precisely defined by the remainder of dividing a number by 2.  Odd and even are thus a really quick way to see if a number is divisible by 2, and since we divide by 2 so much more often than any other number, is really useful...right?  Do we though?  Sure, I think we do divide by 2 more often, but not that much more often.

So, here's the problem with this, in my mind: What about division by 3 or 4 or 5...?  How often do we find we need to split things into 3 groups or more?  Well, plenty often!  So why don't we have an equivalent of even and odd for division by 3?  First, there are infinite numbers we could divide by, so it isn't possible to have special terminology for all potential divisors.  Second, the possible outputs of the modulus operation scales with the magnitude of the divisor.  For example, 3 % 3 = 0, 3 % 4 = 1, 3 % 5 = 2.  So 3 wouldn't just have even and odd. It would have an analog of even, for x % 3 = 0, and then it would need two terms for x % 3 != 0, one for x % 3 = 1, and one for x % 3 = 2.  Sure, we might just combine the two uneven ones, but that's what we already do, when we say something is or isn't divisible by 3, so there really wouldn't be any value in it.  And the kicker: If this is true, then how does "even" and "odd" have more value than just saying something is or isn't divisible by 2?

The answer is this: Even and odd is just terminology for saying whether a number is divisible by 2 or not.  It's not consistent terminology though, because 2 isn't the only number we ever want to divide by, and we don't have any equivalent terminology for any other number.  Further, even and odd only exist within the domain of integers.  In the domain of real numbers, even numbers that are even or odd in the domain of integers are evenly divisible by everything within that domain.  Thus, in the domain of real numbers, either 2 and 4 aren't even, or 3 and 5 are even, because fractional parts allow them all to divide into perfectly equal groups always.  Mathematically though, we can assert that evenness and oddness cannot exist, within a domain that does not have a modulus operation.

What it all comes down to is that even and odd are merely terminology limited to the domains within which all operations and values are discrete.  Outside of computer science, discrete math is limited mostly to casual, day to day math involving coherent objects, and these terms are only useful when dividing by 2, which is common, but not so much more common it needs special treatment.  Given that, I would assert that "odd" and "even", while legitimate properties of whole numbers, are not unique properties that justify their own terminology.

The Problem of Rounding Halves

While the vast majority of posts on this blog are about computer science topics, it was created as a technical blog, not specifically a programming blog.  As such, I may from time to time, write posts on other technical topics.  For example, the topic of this post is math theory.


I have a friend who questions the common practice, taught in public schools in most, if not all, Western countries, of rounding up halves.  For example, say you are rounding to the closest whole number, and you are presented with 0.5.  You round up.  Why?  Is it because 0.5 is closer to 1 than it is to 0?  No, because it isn't.  It's exactly equally distant from both.  The bias for rounding down is exactly equal to the bias for rounding up, so why do we round up on halves?  The answer is that someone decided it should be so.

In theory, math is based on pure, logical rules, but in the case of rounding, the boundary rules are purely arbitrary.  If you are on the boundary of equidistance between your rounding options, you always round up, not because it is a logical rule defined by math itself, but because someone decided it should be done that way, probably to avoid answering what turns out to be a pretty hard question.  But it doesn't really matter does it?  The direction we round on the boundary doesn't need to be logical does it?  Well, let's find out.

Say we have a data set with 10 samples, between 0 and 1.  We want to take an average of the data set, so we can use it to make a decision, and we are treating the samples as votes.  The samples were generated by rating like/dislike for the proposal between 0 and 10, and the results are stored as decimal fractions based on percentage scale (1 is 0.1, 2 is 0.2, etc...).  Because we are treating them like votes, we are rounding them to the nearest whole number.  Any ranking below 0.5 is treated as a vote opposed to the proposal, and any ranking 0.5 or higher is treated as being in favor of the proposal.  This makes sense right?  It's almost exactly how we vote in government elections.  The votes are taken as whole numbers, even though the actual voters are almost never 100% for or against any given candidate.  The vote has to fit into a discrete value.  Now, let's generate a sample set: 0.2, 0.5, 0.3, 0.5, 0.1, 0.5, 0.5, 0.7, 0.2, 0.5.  Reducing these to votes, we get 0, 1, 0, 1, 0, 1, 1, 1, 0, 1.  That's 6 votes for the proposal versus 4 votes against, so we would say the proposal is supported by a 60% majority.  But, the raw data actually shows a very different result.  Four responses are opposed to the proposal, one is in favor of it, and five are balanced.  The average of the raw data is 40%.  So wait, when we round the data, to turn the responses into vote, using the arbitrary round-up-halves rule, we get 60% for, but when we average the unrounded data we get 60% against?  (This is not contrived, I just came up with values where half are on the boundary and the other half are mostly against, and this exact juxtaposition just happened.)  The rounding rule actually flipped the results, so they are the opposite of the true outcome.

So, is there way to overcome this issue?  Is there a mathematically logical solution for rounding halves?  There is, but you aren't going to like it.  The answer is, half of the time round down, and the other half round up.  You might be tempted to say we should just throw out the halves, but when we do that, we get 4 to 1 against with rounding (80% against, rather than the canonical 60%).  Sure, rounding will never perfectly match the raw data, and at least this way makes the most democratic decision, but 20% is a huge deviation from the raw data.  It looks like an overwhelming majority supports the proposal, rather than a moderate majority.  On top of that, there are cases where throwing out the halves will make the wrong decision (for example, in decisions that are more than two ways).

We have a new problem now though: How do we decide which to round which way, and what happens when we have an odd number of halves?  We can't just alternate, because when we do have an odd number of halves, we run into the same problem.  If we always round the first one down, we are favoring rounding down (because on odds one more rounds down than up), and if we always round the first one up, we are favoring rounding up.  Sure, the problem only arises when there is an odd number of halves, and even then the vast majority (all but the final one) are rounding evenly, which significantly reduces the problem, but it doesn't solve it.

There is only one solution: Non-determinism.  Wait, that's just randomness, and randomness isn't logical is it?  Professional mathematicians may be unaware of this or just plain reject it, but at the quantum level, the universe runs on randomness, and thus if randomness isn't logical, the universe isn't logical, and math is an abstraction that doesn't even apply to the universe.  In other words, if math can describe the universe, then yes, randomness is a logical part of math.  The correct answer to the problem of rounding halves is that the direction of rounding should be random, with equal probability.  In education we could simulate this with die rolls, and on very simple rounding problems, students could be expected to provide all possible solutions.  In well crafted simulations, we already do this, often without even realizing it, by using integer data types or abstracting numerical properties in ways that eliminate the rounding problem.  In places where the results are critical, we would want to use true quantum randomness, when rounding halves.  And yeah, sometimes we would still get the wrong outcome, but this is the best math can do, when it comes to rounding halves, and in the long run, at least it will all average out.