-
Notifications
You must be signed in to change notification settings - Fork 25
Description
(from #29) SIUnits.jl has been unmaintained for a while and Unitful is the future. Should be pretty easy to switch.
In general I've struggled a bit to figure out where units are really useful. At first I made SampledSignals pretty unit-full, e.g. samplerate(buf) would return the samplerate in Hz. The issue that came up was that often when I was doing other computations they weren't unitful, so I was constantly needing to strip and add units as I got things in and out of SampledSignals. Where I've come to now is using units as a convenience UI, so you can do things like indexing a buffer in seconds (or Hz for frequency-domain buffers), but generally the API functions return unitless floats, and you don't need to supply unitful arguments in general. I'd be curious to hear your experience for where the units have worked well. (amplify is definitely a case where it's great to be able to use both amplify(0.5) or amplify(-6dB), though it might be nice to just add a * method).
from @haberdashPI
The main advantage I have found in using units is the readability of the API. E.g.
tone(200Hz,500ms)is a lot clearer thantone(200,500). Sometimes I want to compute a number of samples, and there, units are also pretty handy, e.g.floor(Int,50ms * samplerate(sound)). This seems safer and not much more verbose than the unitless version. These are my most common use cases so I decided to return a unitful value from samplerate.
An older version of the API assumed a particular unit and automatically added the units and threw a warning. E.g.
tone(200,500)would throw a warning indicating that it was assuming a 200 second long tone at 500 Hz was the inteded output. However, I never found myself needing to use the functions without units, and it's really easy to add units, so it seems simple enough to just make them errors instead of warnings. It would be easy to change them back to warnings (it just requires changing the definitions of myinHz,insecondsandinframesmethods).
For more general use, maybe it makes sense to have a unitless and unitful version of
samplerate(samplerateandsamplerate_Hz??), or maybe it's easy enough to just writesamplerate(x)*Hzwhen you need the units. Not sure about that.
There is one place where I found units to be annoying, and it is the only place in my current API where I don't employ units: defining a sound in terms of a function, like so:
sound = Sound(t -> 1000t .% 1,2s) |> normpower
A unitful version of this would look something like the following
sound = Sound(t -> uconvert(unit(1),1kHz * t) .% 1,2s) |> normpower
Which I find a lot less readable, and there is little value added.
However, I really think this is a problem with the implementation of Unitful. It seems like in this particular case, Unitful should be able to know that the resulting number is unitless, and should convert the result to an appropriate Real type. Maybe that's an issue I should submit or even make a pull request for...
Regarding amplify, I also implemented
*and/on Real and Sound inputs, but in a chain of operations I find|> amplify(10dB)more readable than|> x -> x*10dB. In a one off manipulation you can still dox = 20dB * x. Perhaps I could eschew amplify if JuliaLang/julia#24990 gets merged, since|> 20dB * _
is fairly readable.
followup from @haberdashPI
Bottom line - the absence of any further impetus, I say the rule should be, unitful inputs, unitless outputs.
I think there's argreement that units are useful as input to functions. But it sounds like unitful outputs can be problematic in your experience.
One argument against avoiding units is that internal calls, not the public the API, can avoid unitful values in general. In Sounds I regularize all inputs using
inHz,insecondsandinsamplesas appropriate, and then all internal functions have well defined interfaces in terms of these three quantities. Thus,
the internals of Sounds doesn't have to worry about stripping units. For instance, right now I have a unitless version ofsamplerate, which I somewhat confusingly callrtype(since it is the rate type parameter, typically denotedRof theSoundobject).
However, I'm currently, slightly inclined to take the conserative approach and change my design in Sounds to use a unitless samplerate to avoid the problems you note. The rule would be to use unitful values for input and unitless values for output. It would be a very consistent approach to
document. It's also not much longer to typesamplerate(x)*Hzthan to type
samplerate(x), when necessary.
The one somewhat akward thing about this approach is that there might be cases where a user wants to pass output, from samplerate for instance, as input to another function in the API, and it seems strange that they'd have to specify the units manually, since we already know what the units are. What I'd really like to be true is for Unitful to know how to strip units away when they cancel out. I've submitted an issue for that here: JuliaPhysics/Unitful.jl#128.
However, that's a hypothetical set of use cases, and the only real use case I can think of where units are more convienient in the output, is for using samplerate to compute some sample length from a duration (also specified as a unitful quantity), for which I already have the method
insamples.