13 ms·
Show HN: Kotlin Money
Manipulating monetary amounts is a common computing chore. However, no mainstream language has a first-class data type for representing money, it’s up to programmers to code abstractions for it. This isn’t an issue per se until dealing with rounding issues from operations like installment payments (e.g., buy now, pay later), foreign exchange, or even simple things like fee processing and tax collection.
Inspired by my days at N26 Brasil dealing with these challenges, I introduce Money: a Kotlin library that makes monetary calculations and allocations easy.
- deleted 2y ago[deleted]
- systems 2y agowhat type of language is kotlin? Is it functional , OOP or something else, which paradigm does it represent?
- deleted 2y ago[deleted]
- eriksencosta 2y agoKotlin is a multi-paradigm language with OOP and FP support.
- lolinder 2y agoIt's all of the above. 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.
- moritzruth 2y agoKotlin was originally designed for running on the JVM, so it is generally object-oriented, but the language and the standard library allow for and encourage a functional coding style. Kotlin is well-suited for DSLs, especially declarative ones (see kotlinx.html[0]). [0] https://github.com/Kotlin/kotlinx.html https://github.com/Kotlin/kotlinx.html
- graypegg 2y agoHuh, I've never actually looked too hard at Kotlin. That is a lot more syntactically flexible than I thought it was! That HTML builder reminds me of Ruby DSLs a lot.
- dtech 2y agoIt's not really syntactically flexible, but made explicit syntax for the use cases other languages used flexibility for, like DSL-builders and extension methods. This avoid the problem that you have in more flexible languages where everyone does a pattern in a slightly different way.
- lolinder 2y agoThis understates how functional Kotlin is—you wouldn't say that Scala is generally OOP with some functional support just because it was built on the JVM. Scala's clearly a functional language with some OOP support. 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.
- ragnese 2y agoI think a comparison to Scala is apt. Working with both languages makes it quite clear to me that Scala is most certainly much more functional than Kotlin. Kotlin is really not very functional in practice, in that it really doesn't encourage a functional style, nor is it optimized for the patterns that are common in functional programming. 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.
- deleted 2y ago[deleted]
- ragnese 2y agoIt's much more "multi-paradigm" or "unopinionated" than Java, but since it is a pretty thin layer over Java and uses its standard library, the ecosystem and idioms are still very much OOP by convention. But, I see the language itself as more akin to C++ in that it really doesn't strongly push much in one direction or another, but they also both lack built-in tools or optimizations for doing real functional programming. So, I'd say that Kotlin, like C++, is an unopinionated language that does well for OOP and/or imperative styles.
- beeforpork 2y agoLike Java, but nicer. Used for Android app devel. Me, I would not call it multi-paradigm, because it really feels primarily like Java (though with many niceties), i.e., it is single dispatch ('this'), objects and classes everywhere. It is completely compatible with Java, and you can mix the languages freely (this is done in Android). It does have standalone functions.
- g-b-r 2y agoA mess, in my limited experience, so far
- speed_spread 2y agoAs a Java replacement, it's still mainly an OOP paradigm with some functional bits added. The type system is mostly unchanged from Java. Kotlin's null safety is interesting, but JVM null pointers are nowhere near the problem they are in C. Otherwise its value proposition mostly relies on overcoming perceived constraints from Java syntax, allowing to redefine parts of the language to build DSLs. Whether this is a good idea is disputable; ask any maintainer of large projects where those capabilities were used in full, or just look at the continuing train wreck that is Gradle.
- atemerev 2y agoCool! As underlying values, do you use integers, bigdecimals, or a decimalized double hack like in OpenHFT?
- eriksencosta 2y agoIt uses BigDecimal. My first goal with the library was to provide a well-designed API. So to keep it simple for myself, I relied on BigDecimal for the calculations.
- atemerev 2y agoI think you are right, API is the most important part; internal implementation can be optimized later.
- vamega 2y agoWhat is the double hack used in OpenHFT? I tried looking this up and came up short.
- pcl 2y agoI’ll byte. Perhaps you didn’t look long enough. I’m sure the answer will float by at some point.
- atemerev 2y agohttps://github.com/OpenHFT/Chronicle-Core/blob/ea/src/main/java/net/openhft/chronicle/core/Maths.java https://github.com/OpenHFT/Chronicle-Core/blob/ea/src/main/j... (see roundN) Be careful and use it only if you know what you are doing and understand the limitations.
- sandGorgon 2y agojust curious - what is the backend api framework that N26 uses ? is it kotlin specific ? or generically spring boot or something ?
- eriksencosta 2y agoIn Brazil (where I worked) we used Kotlin + Ktor. In Europe, they are heavy Kotlin users. References: Brazil: https://blog.eriksen.com.br/en/platform-engineering-n26 https://blog.eriksen.com.br/en/platform-engineering-n26 Europe: https://medium.com/insiden26/engineering-at-n26-a-tour-of-our-tech-stack-and-architecture-9e58ce96f889 https://medium.com/insiden26/engineering-at-n26-a-tour-of-ou...
- getfroggie 2y agoIt's kind of strange that spreadsheet languages don't support money well. Using spreadsheets for escalator style automation is actually quite good and would really be amazing in a language that took typing seriously.
- eriksencosta 2y agoI couldn't agree more as someone who has been using more spreadsheets than actually coding in the last 10 years.
- ebiester 2y agoI think there's a good reason for it to be part of a library. The problem with currencies is much like dates: they're a social construct, and change more than most of the social constructs we embed in programming languages. I don't want to update my interpreter or compiler because Turkey changed their rules of daylight savings time, or Ethereum becomes popular.
- coreload 2y agoYes, and sometimes the context is not just social but legal or contractual, e.g. rounding currency.
- ebiester 2y agoYes, and rounding currency is just something that is always handled, as is the number of places that you take a currency out to - for example, gas is often priced at thousandths of a dollar rather than hundreds but presented to the customer in hundredths at the end. Or Yen in most cases does not have a decimal point, except that the places where you round or don't round can be consequential in large enough quantities. 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!
- benatkin 2y agoFrom my perspective those are tenths of a cent. Stripe has integer values for cents. Am I wrong here? https://stackoverflow.com/questions/35326710/stripe-currency-are-all-amounts-in-cents-100ths-or-does-it-depend-on-curren https://stackoverflow.com/questions/35326710/stripe-currency...
- Ygg2 2y agoNice library! 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?
- eriksencosta 2y agoOh sorry, that's a typo. Thanks for pointing out!
- stefs 2y agoexcept for manipulating time, i'd say
- Etheryte 2y agoDoes this library handle rounding rules [0]? In many countries, prices are rounded to the nearest 5 cent, but the rules can often be elaborate. It looks like the allocation interface might support this, but at the moment I didn't find any mention of it without digging into the docs themselves. [0] https://en.wikipedia.org/wiki/Cash_rounding https://en.wikipedia.org/wiki/Cash_rounding
- eriksencosta 2y agoThis is something I am aware but there is no support for this rounding scheme at the moment.
- amluto 2y agoWait, how do you round? A fixed table from currency to minimum increment? You’re not about to find 1/100-Yen coins, for example.
- criddell 2y agoThings like gasoline can have a price that is a fraction of a cent. The ultimate price is rounded. Canada has these guidelines: https://www.canada.ca/en/revenue-agency/programs/about-canada-revenue-agency-cra/phasing-penny.html https://www.canada.ca/en/revenue-agency/programs/about-canad...
- vetinari 2y agoYou are not going to find 1 or 2 eurocents anymore either, but it is still a valid amount. You can pay that electronically, but not in cash. So rounding for cash is a different problem that rounding money in general.
- cyxxon 2y agoHuh? 1 and 2 eurocents have not been deprecated, afaik only Finland and the Netherlands don't use them anymore...
- shortrounddev2 2y ago> However, no mainstream language has a first-class data type for representing money This is literally the entire point of COBOL
- millerm 2y ago"no mainstream language..." COBOL is has not been a mainstream language for many decades now.
- wiether 2y agoIt's still quite mainstream in domains where they manipulate a lot of money : banking/insurance. The core systems of many old institutions still relies heavily on COBOL.
- eriksencosta 2y agoI think I will add a footnote on COBOL. COBOL is huge in Brazil, lot of insurance/financial companies are still using mainframes.
- throw16180339 2y agoThere are billions of lines of COBOL in production. It's not going away any time soon.
- deleted 2y ago[deleted]
- dlahoda 2y agocrypto needs support for decimals, determinism, rounding directions, uplifting to higher dimensions during long term accrual, down lifting fosome kind of quantization, path dependance. 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.
- hiddew 2y agoHow does it compare to the Java money API (https://jcp.org/en/jsr/detail?id=354 https://jcp.org/en/jsr/detail?id=354) and the related Kotlin DSL in https://github.com/hiddewie/money-kotlin/?tab=readme-ov-file#kotlin-extensions-for-javaxmoney-moneta-jsr-354 https://github.com/hiddewie/money-kotlin/?tab=readme-ov-file...?
- rafaelferreira 2y agoAnother library in this space is Eric Evans' (of DDD fame) Time & Money library https://timeandmoney.sourceforge.net/ https://timeandmoney.sourceforge.net/.
- stickfigure 2y agoI'm surprised nobody has mentioned Joda Money yet: https://www.joda.org/joda-money/ 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.
- nogridbag 2y agoI personally went with Joda money versus the Java money API mentioned above. Our needs are a bit simpler and the Joda Money API is a bit simpler to understand. Our app only deals in USD so I wrote a small utility class to help initialize Money instances so devs don't have to write: Money.of(CurrencyUnit.USD, amount) ...everywhere and do a few other things like total Money instances.
- eriksencosta 2y agoJoda is impressive and has great performance. 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())
- boronine 2y agoI think most of this is covered by a good Decimal API, currency stuff probably shouldn't be embedded into a language because it changes: currencies come and go, get redenominated etc. Although one simple thing that would be useful is keeping track of abstract units, e.g. throwing an error when attempting to do 10 USD + 10 EUR.
- deleted 2y ago[deleted]
- oblio 2y agoDon't we embed timezones, though?
- explorigin 2y agoTimezone conversions don't change by the minute. Currency conversions do.
- oblio 2y agoDo most applications use the minute-to-minute conversions or some daily rate? I'm fairly sure that for example for RON, the Romanian Central Bank only publishes daily rates, for example.
- TZubiri 2y ago[flagged]
- deleted 2y ago[deleted]
- bayindirh 2y ago> However, no mainstream language has a first-class data type for representing money... 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/DecimalFormat.html https://docs.oracle.com/javase/8/docs/api/java/text/DecimalF... [1]: https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/math/BigDecimal.html https://docs.oracle.com/en/java/javase/23/docs/api/java.base...
- jeremyjh 2y agoCan BigDecimal tell me which currency a monetary value is denominated in?
- bayindirh 2y agoNo, but Java has a specification (JSR 354) to build money and currency APIs [0]. A library built upon it can be found here [1]. [0]: https://jcp.org/en/jsr/detail?id=354 https://jcp.org/en/jsr/detail?id=354 [1]: https://javamoney.github.io/ https://javamoney.github.io/
- deleted 2y ago[deleted]
- mhluongo 2y agoTell me you've never worked in fintech without telling me you've never worked in fintech :) 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.
- bayindirh 2y agoSee JSR-354 then: https://jcp.org/en/jsr/detail?id=354 https://jcp.org/en/jsr/detail?id=354 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.
- yafetn 2y agoThe currency codes could probably be inline value classes. That way, you can do val price = 100 money USD Note the lack of quotes around USD.
- sigh_again 2y agoMaintaining an up to date currency list is, quite frankly, hell. Your code will always, always be more up to date than said list.
- 946789987649 2y agoYou could argue the same for timezones in a date library, yet they have them. I would think a library dedicated to money will in fact be the most up to date.
- sigh_again 2y agoDo they ? I've never once had a library that stores Europe_Paris, or Offset_Plus_7_45, they've always been stringly typed. Do you have an example of who'd be crazy enough to maintain a wrapper around tzdb?
- Tainnor 2y agoThis could be mitigated by making the currency interface / abstract class open to extension by the user. The common currencies would be provided by the library and any additional ones could be defined by the user.
- sigh_again 2y agoMaking currencies a concrete implementation is a terrible idea. There is no benefit to it at all, except throwing OOP into something that doesn't need it. A single Money class covers all needed cases, the difference between USD and BTC is... everything. Different smallest denominations, different formats, different everything. You don't even need concrete implementations 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.
- sam0x17 2y agoThis is cool and it's great to see people adding better first-class support for currencies in as many languages as possible! I am the author of a similar crate in the rust ecosystem: https://crates.io/crates/currencies 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/currency.rs#L16 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 https://crates.io/crates/quoth
- deleted 2y ago[deleted]
- xiaodai 2y agocool. whoever uses these libraries better validated it very well.
- occz 2y agoCool stuff! 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.
- nwatson 2y ago`decreaseBy` is a multiplication and subtraction combined, map naturally to commerce domain, and is more complex than plain addition / subtraction.
- sigh_again 2y agoNothing is preventing you from using it this way ? Infix functions are just syntactic sugar, some prefer it, some don't, but there's zero downsides to it (aside from your coworkers abusing it.) 5 money "USD" is literally the exact same thing as 5.money("USD"), and Int.usd = this.money("USD") + 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)
- refulgentis 2y agoThis does a better job of showing an uneasy feeling I have about Kotlin than anything I could say. - 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.
- DaiPlusPlus 2y ago> no mainstream language has a first-class data type for representing money Visual Basic 6 and VGA had a `Currency` type (replaced by `Decimal` in VB.NET): https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/currency-data-type 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-and-smallmoney-transact-sql?view=sql-server-ver16 https://learn.microsoft.com/en-us/sql/t-sql/data-types/money... ...am I missing something?
- sam0x17 2y ago> mainstream ;)
- DaiPlusPlus 2y agoWhat could be more mainstream than VB6?
- eriksencosta 2y agoExcel macros.
- DaiPlusPlus 2y ago> Visual Basic 6 and VGA *VBA, sorry; typo. HN won't let me edit my posts argh.
- psd1 2y ago> no mainstream language has a first-class data type for representing money 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.
- oblio 2y agoOk, what do I do in F# if I want to not think about the low level details of FX conversions, rounding, etc? Which libraries can I use?
- __MatrixMan__ 2y agoI don't know either well, but I took a glance at both and it doesn't seem like UoM is well set up for making decisions based on which peer is going to give you a better exchange rate. We sometimes pretend that money is measuring something, but in reality it's much messier than that.
- chipdart 2y ago> (...) making decisions based on which peer is going to give you a better exchange rate. Neither does a money data type.
- __MatrixMan__ 2y agoIt looks like multiple exchange rates per currency pair is supported: https://github.com/eriksencosta/money/blob/4bef95a1d2158e3088d832217d46407bad87c7ba/money/src/test/kotlin/com/eriksencosta/money/UsageExamples.kt#L248 https://github.com/eriksencosta/money/blob/4bef95a1d2158e308...
- slekker 2y agoSounds interesting! How would it look like? Do you have some code to share?
- bradley13 2y agoLook nice. I do find the wordy operators reminiscent of Cobol. Instead of "subtotal decreaseBy discount" in Kotline I would expect either "subtotal.decreaseBy(discount)" or perhaps "subtotal * (1 - discount)".
- bojanz 2y agoI like the support for custom currencies, as that is an edge case that often pops up. 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/ 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 https://github.com/bojanz/currency
- dkarl 2y agoI'm curious, is there a standard practice of library developers in a certain space collaborating across languages, sharing issues and corner cases and solutions? Is this a common practice with a name? Or is it up to you to look through issues and release notes on projects in the same space to glean useful information?
- andylynch 2y agoNot sure there’s a particular name beyond ‘working group’ or ‘technical committee’. CLDR a really interesting one though, their list of users is quite something too.
- samatman 2y agoI know this is a mere quibble as a rider on a helpful post, but a disclosure is not a disclaimer. 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
- 2y ago
- Exerosis 2y agoMy complaint is, bit too much infix :( What about 50.btc 25.usd Keeps it inline with the time API as well with 20.seconds and what not.
- eriksencosta 2y agoThanks for commenting! I'm still learning Kotlin and I overlooked the usage of get() with vals. This will be the next syntactic sugar into the mix. I think it makes sense. 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.
- Exerosis 2y agoMy only complaint is that there seems to be too much infix. Why not just do 50.btc, 25.3.usd This would keep it inline with the time API doing 20.seconds Also percentages could be standard library if you ask me but would probably also need to be 2.3.percent. Looks cool, always happy to see Kotlin love!
- eriksencosta 2y agoThanks for commenting. I'm still learning Kotlin and I overlooked the usage of get() with vals. This will be the next syntactic sugar into the mix. I think it makes sense. 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.
- eriksencosta 2y agoWhat do you think of this? https://github.com/eriksencosta/money/blob/enum-percentage-spike/money/src/test/kotlin/com/eriksencosta/money/DesignSpikes.kt#L13 https://github.com/eriksencosta/money/blob/enum-percentage-s... 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.
- zellyn 2y agoI'm new to Kotlin. Can someone explain how this function creates a Money object incorporating the given count of them? 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)
- lolinder 2y agoIt'd be easier to follow if you could provide a link to the GitHub line you're looking at, but most likely the `money` function being called is a second extension method of the Number class, which means that it implicitly gets the Number object as `this` and presumably uses it to build the Money.
- zellyn 2y agohttps://github.com/eriksencosta/money/blob/4bef95a1d2158e3088d832217d46407bad87c7ba/money/src/main/kotlin/com/eriksencosta/money/Number.kt#L37 https://github.com/eriksencosta/money/blob/4bef95a1d2158e308...
- lolinder 2y agoYeah, that's just an infix function that provides some default parameters to this extension method: https://github.com/eriksencosta/money/blob/4bef95a1d2158e3088d832217d46407bad87c7ba/money/src/main/kotlin/com/eriksencosta/money/Number.kt#L79 https://github.com/eriksencosta/money/blob/4bef95a1d2158e308...
- thomascgalvin 2y agoIn Kotlin, an infix method can be called without the `.` operator, and without the parens surrounding arguments. 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
- mplewis 2y agoBanks typically use a fixed floating point of 1 integer step = 1/100 cent. Is this value configurable in your library?
- pasc1878 2y agoI doubt Japanese ones do.
- jpollock 2y agoYes, because partial units still have meaning when dealing with interest and taxation.
- eriksencosta 2y agoYep, it is. However, you must create a custom currency to arbitrarily define the minor units you need: https://github.com/eriksencosta/money/blob/trunk/docs/usage/money-currencies.md#custom-currencies https://github.com/eriksencosta/money/blob/trunk/docs/usage/...
- donjigweed 2y agoNice. Looks like it satisfies all the requirements detailed here [0]. Also, good discussion of the major difficulty dealing with money here [1]. [0] https://cs-syd.eu/posts/2022-08-22-how-to-deal-with-money-in-software 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_handling_monetary_values/ik2w72k/ https://www.reddit.com/r/java/comments/wmqv3q/standards_for_...
- textlapse 2y agoKotlin has the problem of "I had N problems, so I think I can create 1 new solution, now I have N+1 problems" (obligatory https://xkcd.com/927/ https://xkcd.com/927/). 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...
- occz 2y agoI think one clear tradeoff that Kotlin - arguably intentionally - makes, is that it's harder to work with than other languages without the use of an IDE. It is after all made by an IDE developer. 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.
- dakiol 2y agoSame feeling. It’s rather painful to read Kotlin code because of so many features that imho are not really that needed… and every developer wants to use them all.
- jeroenhd 2y agoKotlin has a few new features (infix operations for instance) but most of its feature set is stuff other languages have had for years, sometimes decades, written down a little bit differently. There's no functional difference between lambdas in Kotlin and Java, class extensions have been in C# for ages, and operator overloading has been a thing for decades. 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.
- slt2021 2y agoI dont understand the need for this. I always has used integer data type and counted cents. Why need this?
- proper_elb 2y agoMore precision and/or more safety. Will depend on the domain/audience/... the software is being written for.
- eriksencosta 2y agoYou don't need if your solution fits your use case. The library is an implementation of Fowler's Money pattern: https://martinfowler.com/eaaCatalog/money.html https://martinfowler.com/eaaCatalog/money.html
- deleted 2y ago[deleted]
- jsiepkes 2y agoJava has JSR 354 for money [1]. There is also a reference implementation [2]. So there is official support for money in Java. [1] https://jcp.org/en/jsr/detail?id=354 https://jcp.org/en/jsr/detail?id=354 [2] https://javamoney.github.io/ https://javamoney.github.io/
- owlstuffing 2y agoWould be cool to hook this up as a manifold unit[1]. This way you could write code like: 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/manifold-deps-parent/manifold-ext#unit-expressions https://github.com/manifold-systems/manifold/tree/master/man...
- willcipriano 2y agoI'd like types for all the currencies. 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.
- owlstuffing 2y agoWhat you want is Money, currency is a _unit_ type of a Money quantity, just as meter is a unit type of Length. Money cost = 25M USD; cost += 12M EUR; display(cost.to(EUR));
- wtetzner 2y agoI don't think this makes sense? You need to specify an exchange rate somewhere. > just as meter is a unit type of Length The difference is that length units are fixed, but exchange rates are constantly changing.
- afh1 2y ago>However, no mainstream language has a first-class data type for representing money 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.
- wiremine 2y agoNice! Reminds me of the ergonomics of Rebol's money type: https://www.rebol.com/docs/core23/rebolcore-16.html#section-3.4 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.
- eriksencosta 2y agoThis is great!
- rebeccaskinner 2y agoNeat. It seems to me like this is filling three separate gaps: - 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.
- ackfoobar 2y ago> catch at compile time Not in this library, but I think it's possible to put the currency as a type parameter.
- eriksencosta 2y agoThe library will throw an exception if doing an operation with different currencies: https://github.com/eriksencosta/money/blob/trunk/money/src/test/kotlin/com/eriksencosta/money/MoneyTest.kt#L207 https://github.com/eriksencosta/money/blob/trunk/money/src/t... But I think I'll make it explicit in the documentation. Thanks for pointing out!
- yett 2y agoC# has a decimal type: "The decimal type is a 128-bit data type suitable for financial and monetary calculations." https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/types#838-the-decimal-type https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
- nsxwolf 2y agoThat provides the basis for solving precision and rounding issues but for a money library you need more abstractions. The ability to assign a currency code and supply conversion rates and convert between currencies at a minimum.
- tightbookkeeper 2y agoThat’s an application design choice, not a minimum. An alternative is to work internally in one unit and convert at format time.
- maest 2y agoThat is not a viable alternative.
- serial_dev 2y agoEven though my gut reaction is to agree with you, it’s true that it is an app design decision, so in certain scenarios it might very well be a good design decision.
- tightbookkeeper 2y agoShipped applications disagree with you.
- dmoy 2y agoIt really depends on the context of the app 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.
- sgt 2y agoWhat's the best library for JS or TypeScript?
- vintagedave 2y agoDelphi has a currency type, as does C++Builder (so, a native Currency type available in C++ with that toolchain.) 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.
- eriksencosta 2y agoYou are right. Languages indeed have support for currencies and decimal numbers. My implementation is based on Fowler's Money pattern. The Money object is thus a value object representing a monetary amount in a given currency.
- zoogeny 2y agoWhat I want more than a library is an exhaustive test suite for all of the edge cases segmented by currency or finance authority. 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).
- hathawsh 2y agoI have questions about some edge cases I've encountered in dealing with money. - 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?
- eriksencosta 2y agoThanks for the questions. Answers: 1. You get an exception 2. You may specify the rounding rule: https://github.com/eriksencosta/money/blob/trunk/docs/usage/rounding.md 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
- wiseowise 2y agoInfix functions is one of the worst features of Kotlin.
- proper_elb 2y agoFirst, congrats on the library and thank you for sharing it! 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)."
- eriksencosta 2y agoThe currency exchange support is way simpler than this. The library only provides a method that will calculate the monetary amount given another Money object as the rate: 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.
- proper_elb 2y agoThank you for your insights! 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! :)
- serial_dev 2y agoCongrats on the library, I’ll be looking into it for some nuggets of knowledge. 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?
- ppeetteerr 2y agoWhat is up with that API? `1.25.percent()`? `1 money 'USD'`? Love the spirit of a money library, but this is a little odd.
- foooorsyth 2y agoEven as someone familiar with Kotlin, it's hard to decipher what this library is doing without syntax highlighting here. The author is using infix functions and custom extension functions on primitive types. Non-Kotlin-natives will scratch heads.
- ppeetteerr 2y agoOh no, I know how it's done, I'm just surprised by it. There are more straight forward ways of expressing the same thing without using infix functions and those extensions (e.g. `val price = USD(100)` or `val price = Money(100, 'USD')`).
- kens 2y agoThe IBM 1401 mainframe (1959) optionally supported pounds/shillings/pence (£sd) arithmetic in hardware. In those days, there were 12 pence in a shilling and 20 shillings in a pound, so arithmetic with UK money was non-trivial. (This wasn't even microcode; this was literally boards full of transistors to add, subtract, multiply, divide, and format currency values.)
- eriksencosta 2y agoI love to learn more about the history of computing. Thanks.
- eriksencosta 2y agoI'm very grateful so far by the comments. I've learned a lot and it will help me on the next iterations of the library.
- pmarreck 2y agoThis covered the API nicely but said nothing about how the amounts were internally-modeled and how rounding was dealt with. It was only mentioned. 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.
- 0rzech 2y agoNot a mainstream language, but Ada has Annex F - Information Systems, which can be used for currency handling. [1][2] [1] https://ada-lang.io/docs/arm/AA-F/ https://ada-lang.io/docs/arm/AA-F/ [2] https://www.adaic.org/resources/add_content/standards/22rm/html/RM-F.html https://www.adaic.org/resources/add_content/standards/22rm/h...
- pbreit 2y agoAre integers still a preferred method for working with and storing monetary amounts or can we go back to decimals now that most languages handle decimals decently? Monetary amounts pretty much always need to eventually be presented to humans so handling them as integers is quite a pain.
- hansonkd 2y agoA decimal type is sometimes orders of magnitude slower than integer do simple stuff like summing (at least in Postgres and Python). This comes into play if you have to do aggregate functions across tens of thousands of rows and can kill performance compared to integers.
- jacobsenscott 2y agoIt isn't that common to need fractions though. Money is typically handled in integer amounts (we just divide the amounts by 100 or whatever to make it easier for humans to grok). Nobody's going to pay half a cent for a latte for example. If for some reason you do need to deal with 1/1000 of a dollar, or 1/10000 of a dollar, you can still use integers. You just need to know the scaling factor. 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.
- xyst 2y agoI wonder if this would handle small decimals. Edge case? I want to calculate the installments of 265 Wei (0.000000000000000265 ETH) over a period of 144 months
- monkaiju 2y agoI just had to implement the 'allocate' functionality described here, specifically with the %-based weights, and it was a pain that required careful comments to ensure was clear! Would've loved to use this instead. It was fun writing the extra pennies distribution logic tho
- eriksencosta 2y agoIt's a shame I can't travel back in time. Anyway, thanks!
- endgame 2y agoI don't know Kotlin very well. Can it track the currencies involved at the type level, so that trying to combine (say) USD and EUR amounts in an error?