Wow, this thread has grown quite a bit in the last two days. And there's some really good points raised alongside the light trolling here and there. While the discussion around implementation is important and very interesting, I think the question around motivation is critical. Since I see some new discussion around that topic, I'll start there.
Stephen J Turnbull and Paul Moore have asked why the "need" for something other than a library (or perhaps a better library). There are a number of examples that show simple unit calculations and it's easy to argue based on those that nothing further is needed. I think more complicated counter-examples could help push back against this, but complicated counter-examples might be protected IP, or they just might take a lot of work to talk all the way through. Still, I'd love to see a whole thread dedicated to this, because I really am curious to see examples that aren't my own. But I'll start with one example here. It might not be the most compelling, but I'll throw it out there because it's not protected and it has bitten me once. Before I get to this example, though, there is more to arguments for the "need" than just counter-examples. I keep using quotes because nothing is really a need for anything. There isn't a need for built-in support for lists, sets, or even Python, but I sure am glad we have it. Let me say desirability instead. Desirability doesn't just come from things that programmers and CS majors care about, like readability, concise syntax, and self-explanatory code. Desirability for this feature also comes from an operations standpoint. I have made personnel decisions based on whether a candidate has ever used dot-notation in their code before, or if they have only ever used Fotran/Matlab style coding. In regulated environments, risk analysis and mitigation is very much affected by whether a feature has native support or if it comes from a third party library (see https://en.wikipedia.org/wiki/Software_of_unknown_pedigree). That same risk analysis is also heavily impacted by looking at what the worst case scenario might be. And if you want an example of that, look no further than the pre-amble of https://pypi.org/project/units/ (which I believe led to astropy.units). > The Mars Climate Orbiter was intended to enter orbit at an altitude of > 140-150 km (460,000-500,000 ft.) above Mars. However, a navigation error > caused the spacecraft to reach as low as 57 km (190,000 ft.). The spacecraft > was destroyed by atmospheric stresses and friction at this low altitude. The > navigation error arose because a NASA subcontractor (Lockheed Martin) used > Imperial units (pound-seconds) instead of the metric system. I think that a thread of examples along these lines would also go a long way informing both high level and nuanced decisions about what the role of units should be in programming languages and libraries. One last pontification before I get to my example relating to units. We already have examples of features that have both a native implementation and library extensions. int and float are primitives in Python. They are more than enough for most users, but limiting for quite a few other users. So modules like fractions and decimal provide extended support, and libraries like numpy provide even more data types for task-specific needs. Alright, now let's look at an example. Again, it's not my best, let's go with it. This is just a calculation of the expected number of photons to reach a pixel through a camera of a given f-number (F). I mentioned this has bitten me before. All that means is that based on a set of simulations, we though something was possible, spent a few weeks building a prototype, got results that made no sense, and then realized that there was a unit error in the original feasibility analysis. That one was on me, and since I am a capable programmer, I ought to have been using a units package. Symbol definitions: h - Planck's constant c - speed of light Ee - irradiance R - reflectance Q - quantum efficiency F - f-number λ - wavelength a - width of a pixel t - exposure time ȳ - output of luminosity function integral From here, if the triple tick marks do not render this example in monospace for you, then I recommend copy/pasting into something that does. ``` echo no units python -c " h = 6.62607015e-34 c = 299792458 Ee = 200 R = 0.25 Q = 0.63 F = 2.4 λ = 550e-9 a = 3.45e-6 t = 30e-3 ȳ = 683 n = (Ee * R * Q * λ * t * a**2) / (2 * h * c * ȳ * F**2) # n = (200 * 0.25 * 0.63 * 550e-9 * 30e-3 * (3.45e-6)**2) / (2 * h*c * 683 * 2.4**2) print(n) " # Pros - compact, the code representing the equation is easily verifiable, and the magnitudes are also easily verifiable # Cons - no units echo What literals would look like python -c " # h = 6.62607015e-34m2kg / 1s # c = 299792458m / 1s # Ee = 200lx # R = 0.25 # Q = 0.63 # F = 2.4 # λ = 550e-9nm # a = 0.00345mm # t = 30ms # ȳ = 683lm / 1W # n = (Ee * R * Q * λ * t * a**2) / (2 * h * c * ȳ * F**2) # n = (200lx * 0.25 * 0.63 * 500nm * 30ms * (3450nm)**2) / (2 * h*c * 683lm/1W * 2.4**2) " # Pros - Still compact. Dead simple. Planck's constant looks a little weird, but this is usually imported from a library anyway # Cons - requires a syntax change; inline computations like the last line are not IMO quite as readable as the next example echo 'What bracket syntax might look like' python -c " # h = 6.62607015e-34 [m**2*kg/s] # c = 299792458 [m/s] # Ee = 200 [lx] # R = 0.25 # Q = 0.63 # F = 2.4 # λ = 550e-9 [nm] # a = 0.00345 [mm] # t = 30 [ms] # ȳ = 683 [lm/W] # n = (Ee * R * Q * λ * t * a**2) / (2 * h * c * ȳ * F**2) # n = (200[lx] * 0.25 * 0.63 * 500[nm] * 30[ms] * (0.00345[mm])**2) / (2 * h*c * 683[lm/W] * 2.4**2) " # Pros - Still compact, dead simple, and IMO the best way to look at this code # Cons - requires a syntax change and a new kind of namespace in addition to global, nonlocal, and enclosure echo units python -c " from units import unit h = unit('m**2*kg/s') (6.62607015e-34) c = unit('m/s') (299792458) Ee = unit('lx') (200) R = 0.25 Q = 0.63 F = 2.4 λ = unit('nm') (550) a = unit('mm') (0.00345) t = unit('ms') (30) ȳ = unit('lm/W') (683) n = (Ee * R * Q * λ * t * a**2) / (2 * h * c * ȳ * F**2) print(n) n = unit('lx')(200) * 0.25 * 0.63 * unit('nm')(550) * unit('mm')(0.00345)**2 * unit('ms')(30) / (2 * 2.4**2 * h*c) / unit('lm/W')(683) print(n) " # Pros - Has units, and no syntax change required # Cons - less compact # Cons - How do you get the final answer? You need to know that units became astropy.units and see below # Cons - Not dead simple. Multiple adjacent ()() is going to be unpopular with the crowd that uses Python as if it were Fortran/Matlab echo astropy.units python -c " import astropy.units as units h = 6.62607015e-34 * units.m**2*units.kg/units.s c = 299792458 * units.m / units.s Ee = 200 * units.lx R = 0.25 Q = 0.63 F = 2.4 λ = 550 * units.nm a = 0.00345 * units.mm t = 30 * units.ms ȳ = 683 * units.lm / units.W n = (Ee * R * Q * λ * t * a**2) / (2 * h * c * ȳ * F**2) print(n) n = (200*units.lx * 0.25 * 0.63 * 550*units.nm * 30*units.ms * (0.00345*units.mm)**2) / (2 * h*c * 683*units.lm/units.W * 2.4**2) print(n) print(n.decompose()) " # Pros - Has units, and no syntax change required # Cons - less compact, everything is an instance of numpy.ndarray which will break some existing code # Cons - .decompose() is required to convert final result # Cons - dot-notation is better than ()(), but again will encounter resistance from those who use Python as if it were Fortran/Matlab echo astropy.units again, but importing everything into globals python -c " from astropy.units import * h = 6.62607015e-34 * m**2*kg/s c = 299792458 * m/s Ee = 200 * lx R = 0.25 Q = 0.63 F = 2.4 λ = 550 * nm a = 0.00345 * mm t = 30 * ms ȳ = 683 * lm/W n = (Ee * R * Q * λ * t * a**2) / (2 * h * c * ȳ * F**2) print(n) n = (200*lx * 0.25 * 0.63 * 550*nm * 30*ms * (3450*mm)**2) / (2 * h*c * 683*lm/W * 2.4**2) print(n) print(n.decompose()) " # Pros - Compact, has units, and no syntax change required # Cons - Everything is an instance of numpy.ndarray which will break some existing code # Cons - Can't use "m" as a counter (and other namespace collisions) # Cons - .decompose() is required to convert final result # Cons - dot-notation is better than ()(), but again will encounter resistance from those who use Python as if it were Fortran/Matlab echo Quantiphy python -c " from quantiphy import Quantity h = Quantity('6.62607015e-34 m2kg/s' ) c = Quantity('299792458 m/s' ) Ee = Quantity('200 lux' ) R = 0.25 Q = 0.63 F = 2.4 λ = Quantity('550 nm' ) ȳ = Quantity('683 lm/W' ) a = Quantity('0.00345 mm' ) t = Quantity('30 ms' ) n = (Ee * R * Q * λ * t * a**2) / (2 * h * c * ȳ * F**2) #n = I'm not going to bother. print(n) " # Ugh ``` _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/4FP4RRDPODVRMALKPRZKGVEVM7YOP4GP/ Code of Conduct: http://python.org/psf/codeofconduct/