Quadrays again... cross-training in two langauges
I've posted about Quadrays on edu-sig before.... going back and forth between two languages may be a good way to learn both, or to learn one using one's knowledge of the other. In this case, I'm developing the same Quadray class in Python and Clojure. I'll followup with a link to the Clojure version. I'm just beginning with that language and I'm hoping the experts pick it apart some, as I'm sure it's not as Pythonic as it could be. :-D ... actually three languages if you find the links to the C++ version. Kirby """ (cl) K. Urner, MIT License 2015 Python -> Java -> Clojure curriculum 2D + 3D Graphics: Martian Math Topic: Quadrays http://www.grunch.net/synergetics/quadintro.html https://en.wikipedia.org/wiki/Quadray_coordinates Asynchronous Learning Engine (Open Source project) http://controlroom.blogspot.com/2015/08/asynchronous-learning-engine-ale.htm... """ from math import sqrt class Quadray: def __init__(self, OA, OB, OC, OD): self.OA = OA self.OB = OB self.OC = OC self.OD = OD self._norm() def _norm(self): """ Quadrays have at least one element = 0 as one direction is inactive per quadrant """ the_min = min(self.OA, self.OB, self.OC, self.OD) self.OA = self.OA - the_min self.OB = self.OB - the_min self.OC = self.OC - the_min self.OD = self.OD - the_min def __neg__(self): return Quadray(-self.OA, -self.OB, -self.OC, -self.OD) def __add__(self, other): return Quadray(self.OA + other.OA, self.OB + other.OB, self.OC + other.OC, self.OD + other.OD) def __sub__(self, other): return self + (-other) def len(self): """ uses Tom Ace normalization such that the sum of the elements is 0. Simplifies the distance formula, which is set at Edge of Unit Tetra = 2 i.e. D(1,0,0,0) == math.sqrt(6)/2 Tom Ace: http://www.minortriad.com/index.html """ k = sum([self.OA, self.OB, self.OC, self.OD]) / 4.0 t0, t1, t2, t3 = self.OA - k, self.OB - k, self.OC - k, self.OD - k return sqrt(2) * sqrt(sum([t0**2, t1**2, t2**2, t3**2])) def __repr__(self): return 'Quadray(%s, %s, %s, %s)' % (self.OA, self.OB, self.OC, self.OD) v0 = Quadray(1,0,0,0) v1 = Quadray(0,1,0,0) print(v0 - v1) print(v0 + v1) print(-v1) print(v1.len()) # math.sqrt(6)/2 print(-v1.len() + v1.len()) print((v1-v1).len())
On Thu, Aug 6, 2015 at 10:35 AM, Kirby Urner <kurner@oreillyschool.com> wrote:
I've posted about Quadrays on edu-sig before.... going back and forth between two languages may be a good way to learn both, or to learn one using one's knowledge of the other. In this case, I'm developing the same Quadray class in Python and Clojure.
I'll followup with a link to the Clojure version.
This will do for now: https://github.com/4dsolutions/synmods/blob/master/qrays.clj In a Python -> Java -> Clojure spiral we do a lot of the same conceptual stuff at each level. Vector stuff is "ground floor" and graphical, moving up to Website on a server, maybe with Clojurescript for a browser. We start with geometric primitives such as polyhedrons, to go along with our introduction to "objects" in the OO sense. The audience for Websites does not have to be "the public" i.e. a tool used inhouse to do an existing job is more our focus. My background is in medical research, heart stuff in particular. Why not start with the heart as a STEM topic? I want to get away from always doing something eCommerce when teaching website design. Let others stick to that example. Clojure has a lot of embedded Java thinking. Yes, I included Supermarket Math in my Digital Math curriculum, but that doesn't force our hand when it comes to web frameworks. The idea of an interface has migrated to that of a "protocol" which provides a ready namespace for a "record". An interface is like a contract with an empty API: the control panel is specified but no how it's wired, because the same control panel may be used to fly very different types of airplane. Here's what it looks like to specify a Java-style interface an Clojure, which I think use as the same API for two types of vector: (defprotocol VectorOps (add [this other]) (sub [this other]) (len [this]) (neg [this]) (norm [this])) i.e. vectors are things you want to add (A + B), subtract (A - B) get the length of, negate, normalize (put in some canonical form). Both types of vector stick to the abstract algebra notion that A - B is syntactic sugar for A + (-B) i.e. if you have the notion of negation, then you get subtraction for free as to subtract X is simply to add the negative of X. Both types of vector embody that idea, in both languages: Python: def __sub__(self, other): return self + (-other) Closure: (sub [this other] (add this (neg other)))) As I mention on the Clojure list, when coding in an OO language, one has a choice to make ones types more or less immutable. My vectors are "hardened" with immutability in the sense that -A does not "negate A in place" i.e. we do not keep the name A while making the vector point 180 opposite where it pointed before. FP is the enemy of such mutability, but is a way of thinking not just a particular language. In both my vector types, Quadray and XYZ, -A returns a new vector, as do all the vector ops, except len(), which returns a float, the length of said vector. Where I would typically go from here is into "string substitution territory" i.e. interpreting vectors visually, as points and/or edges, inside a 2D or 3D environment. We'd interpolate into POV-Ray and or VRML-like scaffolding, with lots of settings pre-set. This was the object of my stickworks.py. [1] As I joked on the Clojure list, 2D vs. 3D does not necessarily mean flat vs. spatial as both POV-Ray stills VPython's moving displays implement perspective. The screen is 2D in both cases. So forget about the Z axis (so subtract that dimension) i.e. we're not really using it. 3D = 2D (still) + Time, i.e. animation. 2D is manga, 3D is anime. 3D vs 4D is when you bring the Z axis back. That's a curious namespace and probably only used to tickle their brains with this slippery word "dimension" of so many meanings (uses). [2] Back to OOP vis-a-vis FP, we should think about whether we want the same "hardening against mutable state" in our Polyhedron objects, or do we want to allow them to rotate and preserve identity at the same time? Clojure has constructs for that (e.g. atoms), if we decide we really need them. I think we should explore staying rather strictly immutable and consider each frame of film its own polyhedron e.g. icosa = icosa.rotate(A, 60) would output a new polyhedron, to which the same name is rebound (or not, as the case may be). The other option would be icosa.rotate(A, 60) where it rotates "in place" and returns None. Maybe in Python we could to with rotated(P, axis, degrees) and P.rotate(axis, degrees) as the two alternatives, mirroring sorted/sort and reversed/reverse. Kirby [1] http://showmedo.com/videotutorials/video?name=1010040&fromSeriesID=101 [2] In other contexts I use 4D for pure spatial (sans time) because the tetrahedron encapsulates the whole idea of volume and Quadrays use four basis rays, not six, not three. The "threeness" of "three dimensional" relates to either of two zig-zags i.e. 3D + 3D = 6D Tetra -- where D means "diameter of unit sphere" in closest packing. Both Zs hafta be there as 6 is the minimum "cage". But then that's D for diameter. When it comes to Dimension, spatially that's a four, but time/size is always present in addition, so that makes five (and the possibility of special case events i.e. realized 4D shapes). """ Geometers and "schooled" people speak of length, breadth, and height as constituting a hierarchy of three independent dimensional states__"one-dimensional," "two-dimensional," and "three-dimensional"__which can be conjoined like building blocks. But length, breadth, and height simply do not exist independently of one another... [ S. 527.702 ] """ But that's even more remote than before! Praise Allah for namespaces! I'm influenced by Karl Menger and his "geometry of lumps" ideas. http://coffeeshopsnet.blogspot.com/2009/03/res-extensa.html "There is no dimension without time." (S. 527.01)
The idea of an interface has migrated to that of a "protocol" which provides a ready namespace for a "record".
A protocol is more than just an "interface by a different name" or an "interface+namespace". To understand the significance of a protocol, you need to first understand the "expression problem". The expression problem asks you to consider a set of polymorphic functions over multiple datatypes. Without controlling or being able to recompile the original source code, can you easily extend these functions to a new datatype, and can you easily create a new polymorphic function across those datatypes? Typically, OO languages make it easy to add a new datatype (just implement all the methods you want to implement for the new datatype), but hard to add a new method to an object whose source code you can't recompile. Typically, FP languages make it easy to add a new function (just take cases on the datatype in the body of the function), but no good way to add a new case for a new datatype to a function whose source code you can't recompile. Clojure has *two *clean, elegant solutions to the expression problem: protocols and multimethods. Protocols, for example, even let you implement your own new, custom "methods" on Java's built-in string datatype, which is what makes them far more powerful than interfaces. (Look up extend-protocol and extend-type for more info). Most languages don't have a solution to the expression problem. Some claim they have a solution because you can choose what sort of extensbility you are likely to need (am I more likely to add new datatypes or new functions?), and code accordingly using either an FP or OO approach. Python roughly falls into this category, but this is not a true solution to the expression problem. A true solution in Python involves writing your programs using the OO approach, and then google for "Python monkey patching" to look up tricks to inject new methods into classes you don't control -- it's possible, but generally frowned upon... not particularly good style in Python or a design principle we would want to teach students.
On Mon, Aug 10, 2015 at 12:02 PM, Mark Engelberg <mark.engelberg@gmail.com> wrote:
The idea of an interface has migrated to that of a "protocol" which provides a ready namespace for a "record".
A protocol is more than just an "interface by a different name" or an "interface+namespace".
For those not familiar with Java, it's single inheritance, so we don't have the happy solution of injecting new functionality by adding more base classes. In Java, a class is said to "extend" a base class whereas an interface, a set of promised methods, is something a class "implements". Whereas on my extend only one class at a time, said class may implement many interfaces. I see Clojure taps that ability too: http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/extend
To understand the significance of a protocol, you need to first understand the "expression problem". The expression problem asks you to consider a set of polymorphic functions over multiple datatypes. Without controlling or being able to recompile the original source code, can you easily extend these functions to a new datatype, and can you easily create a new polymorphic function across those datatypes?
I like to relate these concepts back to basic math and the different number types we start with, as concentric subsets: N < Z < Q < R < C. Then a protocol or interface is "the operations" such as - / + * < > and so on. What common control panel of operations works across these sets? Clojure, like Java, is not supportive of operator overloading right? I can't go like: (defprotocol VectorOps (__add__ [this other]) (__sub__ [this other]) (len [this]) (__neg__ [this]) (norm [this])) ... and then use (+ A B) to add two vectors, or (- A) to negate one. I think the basics of Polymorphism may be communicated without introducing the wrinkle of not having original source code. Not wanting to change it might be easier for a math student to understand. Not having the original source code sounds like a social problem, an institutional issue, vs. a truly theoretical issue, and that's confusing to newbies. The + and - operators make sense in a Group, where - is both negation and for adding a negative inverse. Add * and / to the mix and we've got a field. Lots of datatypes participate in group and field relationships, including numbers module N, which we may imagine as a specific type of number. That's how I want to anchor the concept of types in Python: in the primitive number types, then add string and collections (sequences, maps...). Mathematics already has a strong sense of "types" which we want to build on. With members of C, we no longer define > or <.
complex(1,0) < complex(2, 0) Traceback (most recent call last): File "<console>", line 1, in <module> TypeError: unorderable types: complex() < complex()
Typically, OO languages make it easy to add a new datatype (just implement all the methods you want to implement for the new datatype), but hard to add a new method to an object whose source code you can't recompile.
Even if you don't touch the original class, you're free to subclass it -- that's a very common way of adding functionality to a type. A lot of the standard library works that way. You're not expected to touch the base class, just subclass and override / extend.
Typically, FP languages make it easy to add a new function (just take cases on the datatype in the body of the function), but no good way to add a new case for a new datatype to a function whose source code you can't recompile.
Clojure has *two *clean, elegant solutions to the expression problem: protocols and multimethods. Protocols, for example, even let you implement your own new, custom "methods" on Java's built-in string datatype, which is what makes them far more powerful than interfaces. (Look up extend-protocol and extend-type for more info).
Most languages don't have a solution to the expression problem. Some claim they have a solution because you can choose what sort of extensbility you are likely to need (am I more likely to add new datatypes or new functions?), and code accordingly using either an FP or OO approach. Python roughly falls into this category, but this is not a true solution to the expression problem. A true solution in Python involves writing your programs using the OO approach, and then google for "Python monkey patching" to look up tricks to inject new methods into classes you don't control -- it's possible, but generally frowned upon... not particularly good style in Python or a design principle we would want to teach students.
Here's an example of injecting methods into an existing class, in the second case using a decorator for that purpose. Usually though, subclassing is the way to extend functionality. Note that there's no storing of methods at the instance level. class Obj: def methodA(self): print("MethodA") def methodB(self): print("MethodB") obj = Obj() print([meth for meth in dir(obj) if "meth" in meth]) def methodC(self): print("MethodC") Obj.methodC = methodC # injecting a method post hoc (not organic!) obj = Obj() print([meth for meth in dir(obj) if "meth" in meth]) obj.methodC() def addmeth(m): """Decorator to inject a method""" def rewrite(Klass): setattr(Klass, m.__name__, m) print("Adding", m) return Klass return rewrite @addmeth(methodC) class Obj: def methodA(self): print("MethodA") def methodB(self): print("MethodB") obj = Obj() print([meth for meth in dir(obj) if "meth" in meth]) obj.methodC() Kirby
On Mon, Aug 10, 2015 at 12:58 PM, Kirby Urner <kurner@oreillyschool.com> wrote:
On Mon, Aug 10, 2015 at 12:02 PM, Mark Engelberg <mark.engelberg@gmail.com
wrote:
The idea of an interface has migrated to that of a "protocol" which provides a ready namespace for a "record".
A protocol is more than just an "interface by a different name" or an "interface+namespace".
For those not familiar with Java, it's single inheritance, so we don't have the happy solution of injecting new functionality by adding more base classes.
In Java, a class is said to "extend" a base class whereas an interface, a set of promised methods, is something a class "implements".
Whereas on my extend only one class at a time, said class may implement many interfaces.
I see Clojure taps that ability too:
http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/extend
To understand the significance of a protocol, you need to first understand the "expression problem". The expression problem asks you to consider a set of polymorphic functions over multiple datatypes. Without controlling or being able to recompile the original source code, can you easily extend these functions to a new datatype, and can you easily create a new polymorphic function across those datatypes?
I like to relate these concepts back to basic math and the different number types we start with, as concentric subsets: N < Z < Q < R < C.
Then a protocol or interface is "the operations" such as - / + * < > and so on. What common control panel of operations works across these sets?
Clojure, like Java, is not supportive of operator overloading right? I can't go like:
(defprotocol VectorOps (__add__ [this other]) (__sub__ [this other]) (len [this]) (__neg__ [this]) (norm [this]))
... and then use (+ A B) to add two vectors, or (- A) to negate one.
I think the basics of Polymorphism may be communicated without introducing the wrinkle of not having original source code. Not wanting to change it might be easier for a math student to understand.
Not having the original source code sounds like a social problem, an institutional issue, vs. a truly theoretical issue, and that's confusing to newbies.
The + and - operators make sense in a Group, where - is both negation and for adding a negative inverse.
Add * and / to the mix and we've got a field.
Lots of datatypes participate in group and field relationships, including numbers module N, which we may imagine as a specific type of number.
Sorry for typos. ... numbers *modulo* N -- given "module" is a Python word, this could be confusing. Here's some source I code I wrote for that type of Number awhile back: http://www.4dsolutions.net/ocn/python/groups.py Kirby
On Mon, Aug 10, 2015 at 12:58 PM, Kirby Urner <kurner@oreillyschool.com> wrote:
Clojure, like Java, is not supportive of operator overloading right? I can't go like:
(defprotocol VectorOps (__add__ [this other]) (__sub__ [this other]) (len [this]) (__neg__ [this]) (norm [this]))
... and then use (+ A B) to add two vectors, or (- A) to negate one.
Python is very interesting in the way that its main arithmetic operators dispatch to specific methods that you can override to achieve a certain degree of operator overloading. For performance reasons, clojure.core/+ isn't something you can directly overload. However, you can replace it in your own namespace with a `+` that has a more complex polymorphic dispatch process and then do whatever you want. For a real-world example of overloading + in this manner to work on vectors and matrices, see: https://github.com/mikera/core.matrix
I think the basics of Polymorphism may be communicated without introducing the wrinkle of not having original source code. Not wanting to change it might be easier for a math student to understand.
Not having the original source code sounds like a social problem, an institutional issue, vs. a truly theoretical issue, and that's confusing to newbies.
I see it as a more fundamental theoretical issue, and relevant to students in the sense that one of the higher aims of software engineering is to figure out how to deliver reusable pieces of code. Even if all the code in the world were publicly available on Github, it would be folly to teach that reuse involves forking and editing code. The expression problem is one meaningful measure of whether your programming language forces you to do that, when what you really want to do is build off of some component in a way that doesn't involve editing the original. This is a sufficiently important principle that we need to take care on how we teach it to students in programming languages that don't have robust support for both axes of function and datatype extensibility.
On Mon, Aug 10, 2015 at 2:03 PM, Mark Engelberg <mark.engelberg@gmail.com> wrote:
For performance reasons, clojure.core/+ isn't something you can directly overload. However, you can replace it in your own namespace with a `+` that has a more complex polymorphic dispatch process and then do whatever you want. For a real-world example of overloading + in this manner to work on vectors and matrices, see: https://github.com/mikera/core.matrix
But I can't do this, right? (defprotocol VectorOps (+ [this other]) (- [this other]) (len [this]) (neg [this]) (norm [this])) I get: WARNING: + already refers to: #'clojure.core/+ in namespace: qrays.qrays, being replaced by: #'qrays.qrays/+ I see from the core.matrix docs that it *can* be done, so obviously I'm just not seeing *how* it's done yet. The books I've looked at don't seem to make operator overloading a priority as much as Python books do.
I think the basics of Polymorphism may be communicated without introducing the wrinkle of not having original source code. Not wanting to change it might be easier for a math student to understand.
Not having the original source code sounds like a social problem, an institutional issue, vs. a truly theoretical issue, and that's confusing to newbies.
I see it as a more fundamental theoretical issue, and relevant to students in the sense that one of the higher aims of software engineering is to figure out how to deliver reusable pieces of code. Even if all the code in the world were publicly available on Github, it would be folly to teach that reuse involves forking and editing code. The expression problem is one meaningful measure of whether your programming language forces you to do that, when what you really want to do is build off of some component in a way that doesn't involve editing the original. This is a sufficiently important principle that we need to take care on how we teach it to students in programming languages that don't have robust support for both axes of function and datatype extensibility.
I understand it put that way. We do not want to redo so much as reuse the old code, on which others depend. It's like wanting to use machines that are already built. Just copying an algorithm over and over, and having innumerable versions, is in no way elegant -- but is what we have, many wheels reinvented. Subclassing was a way OO addressed the issue but without solving it. My understanding is Ruby allows us to keep adding to class definitions. If you wanna add a new method later, just open the class again. More homework: http://bit.ly/1PhdGyZ http://web.engr.oregonstate.edu/~walkiner/teaching/cs609-su14/expression-pro... Thanks for the interesting discussion. Kirby
FYI, I'll be responding offline to this, since the details of how to achieve overloading in Clojure are getting too Clojure-specific, I think, for this list. But I agree this is an interesting discussion.
Yeah, or feel free to take it up on the Clojure list if you think it's a topic other readers might learn from, perhaps with a link back to this discussion. Kirby On Mon, Aug 10, 2015 at 3:39 PM, Mark Engelberg <mark.engelberg@gmail.com> wrote:
FYI, I'll be responding offline to this, since the details of how to achieve overloading in Clojure are getting too Clojure-specific, I think, for this list. But I agree this is an interesting discussion.
participants (3)
-
kirby urner -
Kirby Urner -
Mark Engelberg