The use of infix functions reads a bit weird to me.
If I were to design an API like this in Kotlin, I think I would have gone for regular extensions for many cases and perhaps extension properties, think as such:
val fiveBucks = 5.usd
val fiveBucks = 5.money("USD")
val tenPercent = 10.percent
How come you went for "increaseBy" and "decreaseBy" instead of overloading `plus` and `minus`? Just curious, preference is a valid answer.+ and - are already overloaded (see val subtotal = price + shipping), increase/decreaseBy are for operating over percentages (and could be written as subtotal * (1 - discount), which is much less clear). As the other comment say, it has an actual, real life meaning that people understand clearly. Your price increased by 10 percent. the By convention is also already present in the Kotlin stdlib, although it's more for grouping operations, numeric operations are taking the Of suffix now (sumBy has been deprecated in favor of sumOf, increaseBy could become increaseOf without any loss of clarity)
- The infix is weird and footgun-y.
- Extension methods on int/double serving as constructors smells funny.
- Using infix operators as constructors but not using infix operators for addition/subtraction smells funny.
In general, at least in a corporate environment switching off Java for Android, I found Kotlin a distracting step sideways.
Code reviews tended to involve a lot of bikeshedding over how to make it Kotlin-y, and there's a sort of "why not?" approach to language features that creates much room for the bikeshedding.
It left me feeling like we were unconciously choosing to have the same arguments C++ programmers had in 1990, all over again. Except it was even more destructive, because those arguments were centered, and conflated with "proper" coding in the fancy new language.
I'm not against new and shiny: I was the first to use Kotlin in the org., and I dove right into Swift. There's something alarming with this transition.
I'm heartened by starting to see some debate in Android dev communities about whether Kotlin/Compose were a bridge to nowhere that shouldn't have been a focus for years.
Minor nit: bike shedding refers to, in the original, arguing about the color of the paint on the shed.
It follows that not all discussion is bike shedding.
In this instance, the references to C++ allude to a common best practice for programmers of avoiding custom operators, which goes far beyond an aesthetic, i.e. style, i.e. color of paint on the bike shed difference. There are engineering consequences. This also applies across languages, I'm familiar with it only from trodding the same path you are, through Swift.
Bike shedding bike shedding is indeed possible, so I won't suggest an umabiguous definition. :)
The bike shedding reference in OP is to the different flavors, but equivalent, syntactic sugar that you mention. This uses the new shiny. But this creates toxic baggage, because among other things, because to a naive implementer, there is no solid engineering reason to e.g. avoid custom operators, it's just a scarred C++ graybeard enforcing their opinion :)
and thus complicating teamwork because there is no "right" way
kotlin is great for the lone cowboy. not so much for teams.
Anyway, I'm not a Kotlin die-hard but I found it quite fun to code in the language. IMHO, it has a gentle learning curve and the community has plenty of great libraries (e.g., Ktor, Koin).
Nevertheless, I think I leaned too much on using the syntactic sugar of the language when writing the docs and the introductory article. But by no means users are bound to this way of coding.
I’ve never seen this amount of bikeshedding like in Kotlin. It’s as if whole identity of Kotlin developer is based on “do it differently from Java” which is super ironic for people who use JVM language.
For example, I haven't used them even once.
That said, I, personally, have done almost no Android programming in Kotlin, so perhaps I'm missing the main environment where such things are done? For me, Kotlin has mostly been an aggressive amount of syntactic sugar to make Java a nice experience?
And most of the DSL-style code that I write uses function pointers as the last parameter, so I can do doTheThing(parameters) { anonymous function } and that's about it?
I'm sorry that the organization you work with has aggressive bikeshedding. Perhaps this is something your organization, in general, could have a conversation about?
We've learned a lot about what works and what doesn't in programming languages. Goto considered harmful and all that kind of stuff. If you want to get a Lisp/Haskell fanatic really going point out how so many of the features in those languages have finally made their way into mainstream languages (lambdas, etc.).
What we don't often consider are all of the language features that didn't make it.
This process didn't stop sometime in the past. It is happening right now. That feeling of unease may not be an indication of the quality of the features you are considering. It may largely be uncertainty about what features will or will not stand the test of time.
Perhaps as we get older, we want languages that have all of the good stuff we've learned from the past and none of the experimental stuff that we aren't too sure about yet. That might be because we are starting to notice that we won't have enough time left in our remaining days to sort all the new toys into the good/bad bin.
as OP refers to, custom operators are considered harmful in languages ranging from C++ to Swift. It's a great contemporary example of goto.
I've seen some horrors in my time due to custom operators. I've also suffered through some horrors when they are missing and would clearly be appropriate. They seem appropriate for linear algebra libraries for example. Not everyone agrees, obviously.
As an aside, I have been watching a lot of YouTube videos of people programming C. I notice that goto shows up very frequently, especially in modern C. Usually it is a label at the end of a function to manage resource cleanup before returning, almost like a manual `defer`. Sometimes it is to break out of nested loops. I mean, I've always taken Dijkstra's point to be that long jumping out of a procedural boundary was the real problem, not the keyword "goto". And built-in language features that obviate the need to use goto even for the reasonable use cases are my preference.
I feel the same about custom infix operators. I too share an unease about them. But I have to admit they feel right sometimes. I admit I don't have enough information on their use in this kind of case to know if it is one of the good ones or one of the bad ones.
If it's a foot gun, then I'm not seeing how the gun is loaded?
> Extension methods on int/double serving as constructors smells funny.
It doesn't feel meaningfully different from the static factory pattern in traditional Java, where 10.percent() would be something like MoneyUtils.createPercent() or Percent.create() with several overloads. Under the hood, that is essentially what is happening. Only downside I can see is that it muddies what truly belongs to the class, but that's true of any extension function and Kotlin is intended to be used with IDEA's introspection anyway.
The Kotlin standard library has lots of extension functions that construct new objects.
It also reminds me of Ruby, which perhaps you're also not a fan of and that's OK.
Perhaps I'm misunderstanding your idea, but what about
val total = price + 10.percent * 2
If the price is 10, then the total is 12 or 22?It's possible to avoid making this decision altogether by not implementing `times` for `Percent`, making the user have to be more explicit about what they actually want to happen - for example, requiring them to add parenthesis to arrive at 22:
val total = (price + 10.percent) * 2
Or to arrive at 11: val total = price + (10 * 2).percent
[1] https://kotlinlang.org/docs/reference/grammar.html#expressio...If the price is 10, I would expect total to be 10,2.
(price + 10.percent) * 2 would be 22.
I don't see any way to get 12.
(price + 10.percent) * 2 = (10 + 1) * 2 = 11 * 2 = 22
and price + (10.percent * 2) = 10 + 20.percent = 10 + 2 = 12
Personally I would assume the latter, given standard operator precedence, where multiplication precedes addition. x + y = 100
90% x + 80% y = 84
you translate it to x + y = 100
90/100 x + 80/100 y = 84
and then you use the Gauss method (or whatever) to solve it.So in a math class, when you see 20% you translate it to .2, and 10+20% means 10.2. But in a calculator and in the OP project the % button is magic and and 10+20% means 12.
[1] You have to buy two item that in total cost $100, but today there is a 10% discount in one and a 20% discount in the other so you pay only $84. ¿How much is each item?
Under the usual rules of operator precedence, that would be the expected answer (multiplication having higher precedence than addition).
def sum(x,y):
if is_number(x) and is_number(y):
return x + y
elif is_number(x) and is_percent(y):
return x * (1 + y.value/100)
else:
raise fatal_error
I don't claim it's a good idea, but it's possible if the language allows operator overload.[1] Sorry, my main current language is Racket, but I thought that python-like is easier to read for most people, in spite people that likes python can find like 10 error in 7 LOC.
1) A hint about potential prior art: F# (and/or C#?) have a first-class unit system (physical units I think), which sounds a bit similar to monetary calculations. However, physical unit are easier to model than monetary unit I think.
2) Currently, I am building something tangentially related in Rust: A backtester (test trading strategies on historical and/or simulated data) with focus on accuracy. So one thing I included already is that Assets (like etfs) are valued in a currency.
If I may be so bold: How would you approach these questions / where could I read more about that? 1) When simulating, can I always assume that the exchange will work? (For assets, that is not always the case, sometimes one has to wait a bit until there is a buyer, or there might not be a buyer at all) 2) Is there public domain data in exchange rates? 3) If I have to choose, which exchange rate should I pick? The lower one, higher? What would make the most sense in the context of trading etfs, stocks, options, crypto etc.? 4) How to approach rounding, is there a best practise? 5) I assume it is best to immediately substract taxes in every transaction, even if they are formally defined annually, right? 6) Would you model inflation? I currently plan to ignore it and present it at the very end: "Your portfolio has a final value of X.X ¥. Adjusted for inflation, that would be Y.Y ¥ today (2024-10-08)."
val amount = 1 money "USD" // USD 1.00
val rate = 5.4905 money "BRL" // BRL 5.4905
amount exchange rate // BRL 5.49
It's up to implementers to hook up in other datasets and to consume the rates. Exchange rates datasets differ from country to country and some foreign exchange companies offer short-term contracts to hold a transaction at a given rate (sort of "guaranteed rate/price"). I don't see myself supporting this use case.
Nevertheless, Java Money (Moneta) has this feature. Never used it so, I don't know how it works.
I think it makes sense that this feature would not be planned in your library - as I understand, its goal is to support developers to write better money-related logic, which is sometimes related but different from simulating as accurately as possible.
I just noticed a potential misuse of your API: Transitve relationships: "A" = 2.0000 "B", and "B" = 3.0000 "C", then implicitely, "A" = 6.0000 "C". Can the user now define "A" = 7.0000 "C"?
That would be wrong - but not trivial to prevent, and practically speaking, it is okay I think.
Thank you for your time and for this exchange, wish you good success and fun with kotlin money! :)
With that we can go back to the example:
"A" = 2.0000 "B"
"B" = 3.0000 "C" -> "A" = 6.0000 "C"
"A" = 7.0000 "C"
If you notice this happening in the real world, you have the opportunity to:
1. buy 7 "C" with 1 "A"
2. buy 2 "B" with 6 "C"
3. buy 1 "A" with 2 "B"
4. keep 1 "A" profit
But it's rarely that simple and it often involves 3 or more currencies
On the other hand, be careful about tying the symbol to the currency, as symbols are locale specific. For example, the symbol for USD is $ in eu-US but US$ in en-CA and en-AU (Canada and Australia), and then $US in French locales.
https://cldr.unicode.org/ is the magical dataset behind most good implementations that deal with currency display. Updated twice a year, available in JSON, providing currency symbols and formatting rules for all locales, as well as country => currency mappings and other useful information.
Disclaimer: I maintain a Go solution in this problem space: https://github.com/bojanz/currency
Disclaimers separate you from the comment, examples:
> Disclaimer: I am not a lawyer and this is not legal advice
> [says things about $company] Disclaimer: I don't work for $company, I heard this from someone who does but I can't link to a primary source
Disclosures are additional information which you think it's proper to add, to be open about your interest or stake in the topic:
> Disclosure: I wrote a similar library
> [replies to thing about $company] Disclosure: I used to work for $company
CLDR a really interesting one though, their list of users is quite something too.
Anyway, the library uses the CLDR dataset through ICU: https://github.com/eriksencosta/money/blob/trunk/docs/append...
I don't want to update my interpreter or compiler because Turkey changed their rules of daylight savings time, or Ethereum becomes popular.
These are largely things that can be handled by a library, but if it's in the language you best not get it wrong because it's so much harder to change!
Am I wrong here?
https://stackoverflow.com/questions/35326710/stripe-currency...
Everything is decided to be an 'abstraction' and it's someone else down the line that has to be concerned. Over the last 30 years of starting as a developer I've really lost faith that the majority are actually concerned with solving anything vs just indulging conceptual whims.
I think you're mixing up stuff that's unrelated to your concern.
The concerns you're expressing only apply to operations and financial transactions. That's not handled by spreadsheets, browsers, or databases. In fact, the primary concern of a browser is to provide views over data fed by servers.
In addition, it sounds like you're confusing minor conveniences with something being somehow broken by design. The reason why no one bothered to standardize a money type is the fact that there isn't a technical requirements for it at all.
https://www.rebol.com/docs/core23/rebolcore-16.html#section-...
> $100 + 11 $111.00
> $10 / .50 $20.00
In general, Rebol's type system was very expressive. Would love to see more libraries like this provide that type of experience.
Tangentially, I also often think about super-strict typing. I know people mention some languages that already do this sort of thing (Units of Measure in F# was talked about). It seems silly to me that so many low level programming languages still use uint64, size_t, or char equivalents for the vast majority of the code. Why not `celsius` vs `farenheit`? Or `meters` vs `feet`. That would stop a lot of possible errors. And even more so, if the language supported things like (2 feet * 2 feet) = (4 square feet). Or (10 meters / 5 seconds = 2 m/s).
Looks cool, always happy to see Kotlin love!
It is a (quick) spike solution, but I've implemented the currency codes as enums and added support for 20.percent.
Not so sure about supporting 50.btc and 25.3.usd. I need to check if doing so would affect code completion.
When I was designing the API, I created methods like toUSD() and toEUR(). But with 306+ circulating/tender currencies and 2000+ cryptocurrencies, I thought it could lead to a bad experience when using code completion.
- Let's say I load two money values from a database. Value A is 1 USD and value B is 1 BTC. If I try to add them together, what happens? (I would expect a runtime exception. In fact, I would hope for a runtime exception, because the operation usually indicates a programming error.)
- If I divide $2.00 by 3, do I get $0.66 or $0.67? If I want to specify the rounding rule, is there a simple way to do it? I see there's support for allocation by percentage, but sometimes the allocation rules are more complex and I need to be able to specify the rounding rule.
- Does this library help parse user input? When parsing user input, what happens to extra digits? Does "0.015" get interpreted as $0.01 or $0.02?
Edit: one more question. Sometimes people bend the rules on the number of digits; for example, gas stations may charge $3.599 per gallon. Can the library deal with that or would I have to convert to decimal, multiply, and then convert back to money?
1. You get an exception
2. You may specify the rounding rule: https://github.com/eriksencosta/money/blob/trunk/docs/usage/...
3. It has some parsing capabilities. The extra digit is kept, rounding is only applied when doing an operation with the Money object
4. Same as 2
There's an example for the Swiss Franc in the README (the code is here [1]).
[0] https://github.com/sernamar/dinero
[1] https://github.com/sernamar/dinero/blob/main/src/dinero/roun...
Check for more: https://github.com/eriksencosta/money/blob/trunk/docs/usage/...
So rounding for cash is a different problem that rounding money in general.
You may want to tell that to my wallet, as well as the payments I make with them. There's a cool 37 billion coins outside in the wild, so you're going to find them rather easily throughout Europe, even if your own country has stopped using them.
Technically, you can use them for payment, they are still valid money; but the price will be rounded to 5 cents when paying in cash and you won't get them in the other direction.
It's been years since I last saw a eurocent coin, but it's been eliminated by permitting businesses to remove the need for them, not because they're being phased out.
Canada has these guidelines: https://www.canada.ca/en/revenue-agency/programs/about-canad...
Equally the allocation trick is good, but you’ll find that the people who really care about this stuff really care about the exact algorithm you’re using and who it favours.
Anyway, the allocation may be tweaked to favour where the additional/missing pennies will be allocated. You can find more at: https://github.com/eriksencosta/money/blob/trunk/docs/usage/...
Sometimes you can do all calculations in a single currency and convert at reporting time. I've worked on teams where that was fine.
Sometimes... you really cannot do that.
The OP just described a language feature where you want to multiply euros with usd one place and yen and dinar in another.
You don’t need to do that which is why a decimal or 64 bit int is a fine language approach.
[0] https://cs-syd.eu/posts/2022-08-22-how-to-deal-with-money-in...
[1] https://www.reddit.com/r/java/comments/wmqv3q/standards_for_...
[1] https://jcp.org/en/jsr/detail?id=354 [2] https://javamoney.github.io/
Money ask = 2.1M USD;
What I don't see with the Java JSR is how exchange rate services are integrated (if at all?). In the best of worlds, as with manifold's units I could write.
Money deposit = 300 USD + 500 EUR;
Where internally the deposit automatically maintains the amounts separately and uses exchange rates only for spot display/calc, transfer, etc.
1. https://github.com/manifold-systems/manifold/tree/master/man...
Feels like it should be
Money<USD> ask = 2.1M USD;
Then methods like .plus() wouldn't even be defined for incompatible currencies. Of course that doesn't work for .equals(), so you'd still have to fall back to exceptions in that case.For instance, physical dimensions store values as SI units, regardless of supplied unit, but retain the supplied unit type as a display hint.
Here, the Length dimension illustrates working with amounts of any unit.
Length orbit = 250 mi;
Length moon = 238000 km;
Length landing = moon + orbit;
display(landing); // prints 148,136.344 mi
display(landing.to(km)); // prints 238,402.336 km
// where
display(landing.to(mi) == landing.to(km)); // prints true
// perhaps a better example
Rate rate = 1.41 km/s;
Time duration = 76 hours;
Length distance = rate * duration;
// make your own unit constants
RateUnit kps = km/s;
rate = 1.41 kps;
You can play with the manifold-science[1] project, it implements the complete SI physical dimensions and many derived dimensions along with comprehensive operator overloading[2] support etc. to make working with and reading code using quantities more productive in Java.
1. https://github.com/manifold-systems/manifold/tree/master/man...
2. https://github.com/manifold-systems/manifold/tree/master/man...
USD rent = 2000
EUR salary = 6000.53
USD disposableIncome = salary - rent
Then:
disposableIncome.toDecimal()
disposableIncome.toInteger()
disposableIncome.toString()
USD interest = disposableIncome.compoundInterest(startDate, endDate, apy)
USD tip = billTotal.tip(percentage)
USD and EUR would inherit from Money
Not sure how to implement it, might be a JVM magic thing.
Money cost = 25M USD;
cost += 12M EUR;
display(cost.to(EUR));
> just as meter is a unit type of Length
The difference is that length units are fixed, but exchange rates are constantly changing.
Right. As I mentioned earlier exchange rate services would need to be integrated into the API.
Also, exchange rate services would not be needed until a conversion is necessary. For instance, when adding money of different currencies internally the Money type maintains currencies separately, subtotals for USD, EUR, etc. are managed. If at a later time the sum of Money should be displayed, deposited, etc. as a single currency, only then is an exchange service involved.
Using Java library will limit your program only to jvm platform. Using a kotlin library like this one (given it's written in pure kotlin and doesn't uses any jvm platforms specifics) will allow you to build it to whatever target kmp supports (macosx, ios, android, js, mingw...)
Plenty of languages (also Java with Graal, TeaVM, J2CL, etc.) can also target these platforms.
But as with all these languages targeting these platforms (including Kotlin) you are going to have to call platform specific API if you want to do anything useful. There are also platforms specific limitations which means certain stuff which you can do in the language doesn't work on those platforms.
https://www.joda.org/joda-money/
From the same person that brought us Joda Time (ie, what the java time API was based on). I've used Joda Money a lot and it's great.
Honestly I prefer APIs that look like APIs and I think this trend towards inventing DSLs is a bad one. Rails works because there's a critical mass of people who have adopted what is essentially a whole new language on top of Ruby. A money library doesn't warrant a new language, it's unnecessary cognitive load. This new money library would look fine with simple constructors and method calls.
The examples were written using the infix notation but you can just use regular method calls. For example:
val price = Money.of(100, "USD")
val shipping = Money.of(5, "USD")
val subtotal = price.plus(shipping)
val discount = Percentage.of(10)
val total = subtotal.decreaseBy(discount)
total.allocate(2)
total.allocate(60.percent(), 40.percent())
Money.of(CurrencyUnit.USD, amount)
...everywhere and do a few other things like total Money instances.Manipulating money is probably trickiest thing since time was invented. Library looks very usable.
I have to ask though:
> val transactionFee = 1.25.percent() // 1.5%
How is it 1.5?
The 'money/percent/usd' are almost like new keywords - which to me seems bad. Why not a formatter or another tool that simplifies but doesn't make it too clever or worse, hard to read.
Kotlin is really cool and arguably better than Java. But the language is very hard to read ('easy' to write). And with so many different ways of doing a single thing (and many of them exist to support Java, I understand) plus new 'macro-like' things expanding the language automagically makes it very hard to grasp.
I hope I am not the only one with this same feeling...
It's also quite a bit more liberal in introducing new features than Java is, and with a larger feature surface there's more potential for misuse.
On balance, I think using Kotlin over Java is worth it, though.
Most of the resistence to Kotlin I see seems to come down to "I don't know the syntax and that makes it hard for me to understand the language" or "this isn't like the language I prefer".
Kotlin actually brings surprisingly few language features to the table, its API is just very effective at making use of the few new features it brings as opposed to the ossified languages from a few decades ago that offer new features but then decide not to make use of them in the standard library in case a programmer doesn't like using part of the language.
Criticising Kotlin for using things like infix operators or lambdas is like criticising Java for shoving classes and generics down everyone's throat. You can write Java with only minimal use of these, but you'd end up with a weird, non-standard code style for the language. There are still plenty of programmers around that despise the concept of classes or inheritance and will declare any Java/C#/C++/Javascript/Go program unreadable for making use of OOP concepts, not dissimilar to the "I don't get Kotlin" people that seem to have been tricked into believing Kotlin is supposed to look like Java.
I had the same immediate reaction. This style of code is something the Scala community used to be in love with, and was criticized for it. The community has largely moved away from this style, so it's interesting to see history possibly repeating itself with Kotlin.
[1] https://ada-lang.io/docs/arm/AA-F/
[2] https://www.adaic.org/resources/add_content/standards/22rm/h...
Visual Basic 6 and VGA had a `Currency` type (replaced by `Decimal` in VB.NET): https://learn.microsoft.com/en-us/office/vba/language/refere...
T-SQL has `money` and `smallmoney` types: https://learn.microsoft.com/en-us/sql/t-sql/data-types/money...
...am I missing something?
This looks like it's ignoring the Number and creating a new Money object (of denomination 1?)
> public infix fun Number.money(currency: String): Money = money(currency, defaultRoundingMode, null)
100 money "USD"
is the same as 100.money("USD")
`Number.money(currency: String): Money` is an extension method; Kotlin provides syntactic sugar which allows you to "add" methods to existing classes. The above is basically equivalent to: fun money(count: Number, currency: String): Money
I allowed myself to be distracted by all the weird infix stuff. I was thinking "but it's not passing itself into the money constructor" but it's actually just delegating to a different-signature version of `money` _on itself_ that will use its value then.
https://github.com/eriksencosta/money/blob/4bef95a1d2158e308...
However, the Kotlin code looks absolutely… wrong? Strange? It just feels like a lot of magic to end up with ugly code.
This is all subjective, so my question is only this: is this how Kotlin is like in 2024?
Parcal has a Currency type. Though, I can understand not calling it mainstream... Fun fact it's a fixed point type which is just a Int64 behind the scenes, at least in Delphi.
val price = 100 money USD
Note the lack of quotes around USD. private data class Money<T>(val name: String, val amount: Int) {
operator fun <U : T> plus(other: Money<U>): Money<T> {
return Money(name, amount + other.amount)
}
fun <U> plus(other: Money<U>, converter: (Money<U>) -> Money<T>): Money<T> {
return converter(other) + this
}
}
private object EUR
private object USD
val eur = Money<EUR>("EUR", 10)
val usd = Money<USD>("USD", 20)
usd + eur // error
This gives you entirely user defined currencies, does not pollute the global scope with unneeded currencies, allows you to plug in any conversion technique (pop off, make a network call), and fails if you try to add USD and EUR without converting one into the other.Currencies should always be the user's responsibility to provide.
I feel like you're jumping to wild conclusions. All I'm suggesting is to change your code to
private object EUR : Currency
private object USD : Currency
data class Money<C : Currency>(...) { ... }
That's just an empty tag interface and that's not particularly hardcore OOP, you can do the same thing with Haskell typeclasses.I had an idea for a fixed-decimal class (that would also be useful for a money class) that held all inflight amounts as fractions and only did the division and float rounding when you needed to view the amount.
Edge case?
I want to calculate the installments of 265 Wei (0.000000000000000265 ETH) over a period of 144 months
- fixed point arithmetic
- tagging values with units
- localization for parsing currency names into units
I'm curious if Kotlin's type system is expressive enough to allow you to catch at compile time cases where you might be trying to, e.g. add USD to GBP.But I think I'll make it explicit in the documentation. Thanks for pointing out!
Not in this library, but I think it's possible to put the currency as a type parameter.
This one seems to carry units, as in, it is not a type with math for a known amount of fixed point precision that differentiates this library, but that it captures that plus the unit -- the currency, USD, EUR, etc -- that it is in.
It was fun writing the extra pennies distribution logic tho
Your presentation layer is probably always going to need to do some manipulation of the number anyway (put a dollar sign on it, etc) so using a decimal amount just for presentation needs isn't a huge win. But it will work too.
This comes into play if you have to do aggregate functions across tens of thousands of rows and can kill performance compared to integers.
(see roundN)
Be careful and use it only if you know what you are doing and understand the limitations.
When I was designing the API, I created methods like toUSD() and toEUR(). But with 306+ circulating/tender currencies and 2000+ cryptocurrencies, I thought it could lead to a bad experience when using code completion.
References:
Brazil: https://blog.eriksen.com.br/en/platform-engineering-n26 Europe: https://medium.com/insiden26/engineering-at-n26-a-tour-of-ou...
I am the author of a similar crate in the rust ecosystem: https://crates.io/crates/currencies
major features include:
* support for all ISO-4217 currencies (though not all have been explicitly tested as it is hard to find native users of some)
* compile-time macros for specifying an Amount in the native format (with symbol, etc)
* support for non-base-10 number systems (there are a few ISO currencies that needed this)
* every currency uses an appropriate backing data type, and new currencies and backing data types can be defined as long as they meet the trait requirements
* opt-in ability to enforce only checked math ops (but using the usual +,/,-,* etc symbols). This is critically important for crypto and finance applications where a panicking math op can, for example, brick a blockchain or real-time trading system
* support for parsing and printing currencies in their native format at runtime
* currencies use the appropriate format style (https://github.com/sam0x17/currencies/blob/main/core/src/cur..., i.e. symbol can be "suffix attached", "suffix spaced", "prefix attached", "prefix spaced")
* support for a number of cryptocurrencies, basically popular ones and ones I've bothered to add. Will always accept PRs adding others!
* ability to define your own currencies using the `define_currency!` macro. Though these will not be supported by the built-in `amt!` macro unless I add them to the crate.
e.g., here is how a few of the core currencies are defined:
define_currency!(USD, u64, 1_00, "$", "United States Dollar", PrefixAttached, true, false);
define_currency!(BTC, u64, 1_00000000, "BTC", "Bitcoin", SuffixSpaced, false, true);
define_currency!(ETH, U256, u64_to_u256(1_000000000000000000), "ETH", "Ethereum", SuffixSpaced, false, true);
One disadvantage right now is there is no ability to have a generic "amount of some arbitrary currency" other than through generics, as the underlying traits aren't object-safe. A good way to work around this is to define an enum that contains all the currencies you plan to support. I am working on a feature that will let you easily generate this enum at compile-time :)
parsing is done using my Quoth parsing crate which provides a very safe, lexer-less way to do parsing of UTF-8 strings that relies on recursive parsing in a way somewhat similar to syn, but there are no token streams https://crates.io/crates/quoth
For cryptocurrency data, take a look at the DTIF.org dataset: https://github.com/eriksencosta/money/blob/trunk/docs/append...
I don't think that's correct, absent some no-true-scotsman gymnastics.
F# has units-of-measure (UoM) out of the box, and it supports decimal numbers. I've come across a python library for UoM as well.
The big problem with handling money in code is not, IMO, the rounding (your allocate function is a nice utility but it's not core); it's unit confusion - adding baht to ren mi bi, adding cents to euros, etc. This problem is very well solved by F#'s UoM.
We sometimes pretend that money is measuring something, but in reality it's much messier than that.
Neither does a money data type.
https://gist.github.com/williamcotton/b67ff641eeec7673131715...
Yeah, I think OP's claim is not valid. Even .NET provides System.Decimal, which by design and explicitly mentions financial calculations.
Taken from the intro docs:
https://learn.microsoft.com/en-us/dotnet/fundamentals/runtim...
> The Decimal value type is appropriate for financial calculations that require large numbers of significant integral and fractional digits and no round-off errors.
> The decimal module incorporates a notion of significant places so that 1.30 + 1.20 is 2.50. The trailing zero is kept to indicate significance. This is the customary presentation for monetary applications. For multiplication, the “schoolbook” approach uses all the figures in the multiplicands. For instance, 1.3 * 1.2 gives 1.56 while 1.30 * 1.20 gives 1.5600.
I beg to differ. Java has "Decimal" class which guarantees to be safe from IEEE754 floating number side effects, and specially created to handle cases like money and financial calculations.
In these days it's used as BigDecimal, it seems [1].
[0]: https://docs.oracle.com/javase/8/docs/api/java/text/DecimalF... [1]: https://docs.oracle.com/en/java/javase/23/docs/api/java.base...
A library built upon it can be found here [1].
Decimals aren't enough. You have frequent currency conversions and all sorts of other chores. Using a fixed-decimal datatype doesn't solve those problems by itself, it's just a tactic.
Yes, I never worked in fintech, but lots of my family members work or worked in banking sector. So, I'm not an alien when it comes to money, and how it works and processed in IT side of the things.
This is literally the entire point of COBOL
The core systems of many old institutions still relies heavily on COBOL.
I'm fairly sure that for example for RON, the Romanian Central Bank only publishes daily rates, for example.
Is it functional , OOP or something else, which paradigm does it represent?
Most modern languages (including everything from Java to OCaml) don't fit neatly in any one box because they have added a bunch of features from other paradigms that make multi-paradigm programming possible. Kotlin is that way to an extreme, because instead of strapping on functional features to an OOP core like Java did it was designed out of the gate to be all of the above.
When I program in Kotlin I'm constantly shifting between "paradigms" based on what is actually needed in the moment. It's one of the best languages I've ever worked with for learning the strengths and weaknesses of different programming styles because it has such strong support for most of them.
Kotlin is well-suited for DSLs, especially declarative ones (see kotlinx.html[0]).
Kotlin, in turn, is very balanced. The standard collection library uses OOP syntax (chains of method calls), but is extremely functional in its philosophy towards how we think about manipulating collections.
Scala has `Try` and `Either` for modeling domain failures as values as opposed to throwing (unchecked) exceptions, which are side-effecting. Scala also has for-comprehension syntax built in to make it more convenient to compose and chain fallible operations that use `Try`, `Either, `Option`, etc. Kotlin's `Result` type is not designed for modeling fallible operations (not a sealed class, no type parameter on the error variant, error must be `Throwable`, etc), and Kotlin does not offer convenient syntax like for-comprehensions despite being more than able to (given that many of us have implemented near-perfect analogues to Scala's `Try` and for-comprehension syntax in Kotlin). Similarly, no official Kotlin libraries or APIs ever return errors as values and always opt for throwing exceptions instead.
Scala's collection types are implemented as persistent collections, which are optimized for cheap updates. If you want to avoid direct mutation in Kotlin, you have to make a full copy of a collection.
Scala's mutable and immutable collection types are actually distinct from each other and cannot be used interchangeably. In Kotlin, List<T> is a supertype of MutableList<T>, which means I can pass a MutableList into a function that expects a List. That means that the list can be changed in another thread while my function is running, so I can't even assume that checking `list.size` at two different points in my function will return the same value.
Scala has actual type classes. Kotlin has extension functions which are not nearly as useful (and they have very surprising semantics when it comes to static vs dynamic dispatch). Type classes are certainly not required for functional programming with a statically typed language, but it definitely helps when it comes to modeling things without needing to lean on writing more classes and/or utilizing inheritance.
Also, Kotlin's "functional" APIs on collections are much more janky than Scala's. For example, if I have a `Set<T>` and I call `.map((T) -> R)` on it, Kotlin will give me a `List<R>` while Scala will give me a `Set<R>`, which makes way more sense.
Kotlin is cool, and it would be dishonest for me to say that it's not at all functional, but after having worked in other many other languages, I'm very comfortable saying that Kotlin is an OOP/imperative language first with some functional stuff added in (sealed classes, convenient lambda syntax, top-level functions, and some of the typical collection combinator APIs). Whereas Scala is quite clearly designed to be actually GOOD for functional programming without taking an extra-hard performance hit.
[0] https://youtrack.jetbrains.com/issue/KT-68296 [1] https://docs.scala-lang.org/scala3/reference/experimental/ca...
I always found checked exceptions (Java) to be mostly fine/good. I sincerely believe that a lot of the hate for them in the last decade is just cargo culting. I get a small dose of schadenfreude when I see someone doing mental contortions to simultaneously explain why Rust's Result type and handling (and similar features in other langs) is awesome, and Java's checked exceptions are terrible and definitely not 95% similar in DX and semantics...
The one point against checked exceptions for me is that if I'm trying to model a domain failure, it really doesn't make sense to collect a stack trace. For example, if I'm writing a logIn function that takes a username and password, then it's totally normal for the username and password to be invalid. Why would I want to spend the CPU time collecting a stack trace when someone simply typed in an incorrect password? Do we want to collect a stack trace when the password is correct and the user gets logged in?
So, in that sense, I do have a small preference toward expected failure modeling to be somehow different from "true" "exceptions".
I do also agree with you that Kotlin's biggest original sin was to throw away checked exceptions without replacing the concept with ANYTHING. Of course, as you've pointed out, they're backtracking on that somewhat by trying to add this concept of errors as a kind of ad-hoc union type. (aside: they also backtracked on their choice to not have type classes because "extension functions are good enough" by trying to do context receivers, which is ending up being really hard and probably more complex than just doing damned type classes in the first place...)
I do kind of like the direction that Swift is moving with finally adding specifically typed throw signatures (essentially Swift now has checked exceptions, but they're aren't actually traditional exceptions because they don't collect stack traces and unwind the stack- they're just syntax sugar around a Result/Try type).
But, my prediction is that the pendulum is starting to swing back in favor of checked exceptions. In the next decade we'll continue seeing languages adopt mechanisms that are essentially checked exceptions. But, they will be slightly different and definitely called something else so that we don't have to admit that we were wrong to shit on the idea for 15 years.
EDIT: Also, I do follow Kotlin developments closely, but I haven't actually worked in Scala for several years, so I had no idea about this capabilities idea. Thanks for the link.
FWIW you can override the stack trace collecting behaviour of Java exceptions. Not collecting the stack trace makes exceptions really fast and thats actually how Scala is implementing their boundary/break feature. I do kind of wish that Java could backtrack and that the stack trace would only be filled in on RuntimeExceptions that are true panics.
I really feel like Java just needs investment on the language syntax to make checked exceptions good. Things like `try!` or `try?` from Swift would be nice and taking Scala's try { as an expression with case catch blocks would make it really fluent. I think most devs can agree that they want to know what errors can happen, but currently in Java its just a pain to deal with them. Brian Goetz originally had some ideas around evolving the switch construct for this [0] so at least we know making exceptions better is on his radar.
I use it in almost every project because it works so well with StateFlow: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-corout...
I can install a persistent collections library in any language. I know for a fact there are persistent collections libraries for Rust, JavaScript, Java, etc.
But since none of those are built-in, they aren't used by the standard library APIs, and they won't be used throughout the wider ecosystem.
And, I'm still not willing to call Java a "functional language" because I can install a third-party library for persistent/immutable collections and hopefully avoid the built-in collections as much as possible.
This avoid the problem that you have in more flexible languages where everyone does a pattern in a slightly different way.
I always has used integer data type and counted cents. Why need this?
eth is whole number 10*18. usdc is 10*6.
usd is if to speak is 10*2 number.
solana eth price is less than eth eth price because of bridge risk.
etc.
there are on decimal money to out of crypto.
there are logarithmic money in crypto.
so many many moneys.