Kirby writes: I guess I'm feeling uneasy with a foundational approach that doesn't bring in the concept of objects closer to the beginning. The object concept is an important one. Just procedures and data, without objects, seems a rather scattered/cluttered approach. Shouldn't newcomers learn the object model right about where this rational numbers example is presented? Certainly building rational numbers as objects would be a standard exercise in a Python-focused curriculum (might be a math class -- if the distinction matters). You need to distinguish several things: - class-based programming & object-oriented computation - object-oriented programming & object-oriented computation - modules and representation hiding Functional programmers prefer modules over classes. In PLT Scheme we use both. The idea is simple. You write a module and you export just as much as you wish. For example, (define-signature Sequence^ ( build-sequence ; Element ... Element -> Sequence iterator ; Sequence (Element -> X) -> Void )) (define Sequence@ (unit/sig (Sequence^) (import ...) ; imports ; ------------------------------------------------------- ; body (define-struct hide (>sequence)) ; a structure for encapsulating stuff (define (build-sequence . x) (make-hide ... x ...)) (define (iterator sequence function) (let ([internal-representation (hide->sequence sequence)]) ...)))) defines a module that creates an opaque type sequence by exporting constructor and an iterator but no tools to get at the internal representation. In other words, it is a mechanism for coupling data and functions in an intimate manner (and units are first-class values on top). It looks even better if you write this with ML signatures: signature GROUP = sig type Element val plus : Element Element -> Element val id : Element val inv : Element -> Element end functor Group(type E) : GROUP = struct type Element = E; ... end and you can parameterize functors and units over imported mathematical structures: functor VectorSpace(structure Field) : VECTORSPACE = struct ... end and then you can use Real or Complex or some other field to build vector spaces. -- Matthias P.S. Someone asked whether I'd use ML first. No. Kids have enough trouble understanding computation. Type checking and proves are confusing them even more.
I'm glad to know there's a module system of such sophistication. Also, whereas the chapter I was citing introduces the rational number by means of numerous Scheme procedures, it wouldn't make a lot of sense to go to all this trouble in PLT Scheme, where the fraction type is built in, e.g.:
(define a (/ 3 4)) a 3/4 (define b (/ 1 12)) (+ a b ) 5/6 (- a b) 2/3 (* a b) 1/16 (/ a b) 9
In Python, I'd do the same re modules: store the Fraction class in a module, then import. I happen to have one handy:
from mathobjects import Fraction
modules bear some resemblance to classes in that both use the dictionary structure to define a table of contents. You can get at the keys to a class with dir:
dir(Fraction) ['__abs__', '__add__', '__div__', '__doc__', '__eq__', '__float__', '__gt__', '__init__', '__lt__', '__module__', '__mul__', '__neg__', '__pow__', '__radd__', '__rdiv__', '__repr__', '__rmul__', '__sub__', 'denom', 'fformat', 'float', 'list', 'mkfract', 'numer', 'recip', 'simplify']
Same with a module:
import mathobjects dir(mathobjects) ['Fraction', 'Matrix', 'Poly', 'Sqmatrix', '__builtins__', '__doc__', '__file__', '__name__', '__path__', 'deriv', 'polynomial', 'pyfraction', 'simplematrix']
Then use the Fraction class to instantiate objects, a and b. Because the primitive operators have been overridden, your fraction objects behave as expected:
a = Fraction(3,4) b = Fraction(1,12) a+b (5/6.) a-b (2/3.) a*b (1/16.) a/b 9
It was my decision, in the __repr__ method, to have that little decimal point in the denominator. This allows conversion to float simply by evaluating a stringified representation:
eval(str(a+b)) 0.83333333333333337
although __float__ is also defined:
float(a+b) 0.83333333333333337
Using a parallel infrastructure, we would define polyhedra as another kind of object, based on a list of coefficients as parameters:
from mathobjects import Poly p = Poly([1,2,3,4]) p x**3 + 2*x**2 + 3*x + 4 q = Poly([3,-2,-1,-1]) p*q 3*x**6 + 4*x**5 + 4*x**4 + 3*x**3 - 13*x**2 - 7*x - 4 p-q -2*x**3 + 4*x**2 + 4*x + 5 p+q 4*x**3 + 2*x + 3
A fully generalized system would allow me to use polynomials in the numerator and denominator of Fractions, but so far my polynomial objects don't understand the / operator -- dividing polys is somewhat difficult, especially if you plan to simplify them (which involves finding common factors, i.e. the gcd). However, I *am* able to use the Fraction objects as coefficients if I like:
r = Poly([a,b,1]) r (3/4.)*x**2 + (1/12.)*x + 1 r*p (3/4.)*x**5 + (19/12.)*x**4 + (41/12.)*x**3 + (21/4.)*x**2 + (10/3.)*x + 4
And to pass values to the polynomial, as a function:
s = r*p s(10) (284437/3.) s(-12) -158976
Note that s(10) returned a fraction, not a decimal. This is because my evaluator processes coefficients as fractions, in order to make sure we don't loose any information as a result of casting to floating point. It was by building polynomials such as the above, and passing values to them, that I developed the Bernoulli numbers:
bernoulli.bernseries(14) [1, (1/2.), (1/6.), 0, (-1/30.), 0, (1/42.), 0, (-1/30.), 0, (5/66.), 0, (-691/2730.), 0, (7/6.)]
The Matrix object, with subclass Sqmatrix (square matrix) works the same way, with *, ** (power), +, - and / overrridden. The elements of a matrix may be Fractions or Polys. So the idea is to learn about mathematical objects by building them, just as we build radios or other models to learn about their design and structure. The result is, in this case, a primitive computer algebra system. In math class, we would spend more time talking about fractions, lcm, gcd, the determinant of a matrix and so on, and less time worrying about the syntax and infrastructure of Python, which presumably they picked up somewhat earlier -- maybe in 8th grade, whereas by this time we're in high school. I gather you take a similar approach with Scheme, focusing increasingly on the knowledge domain as time goes on, with Scheme itself receiving less emphasis. Kirby
participants (2)
-
Kirby Urner
-
Matthias Felleisen