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/

Reply via email to