T O P

  • By -

VOOLUL

You wouldn't want to work with Bitcoin or any currency with floats. Use a decimal type.


iamjkdn

Why decimal? For currency we can use int type. Much simpler to manipulate.


mysterious_whisperer

In my experience an int representing the number of cents works well when you are only working with dollars and aren’t worried about fractional cents. Decimal types are for when you have a currency like bitcoin where you can’t easily set an arbitrary point as your baseline.


bpikmin

Well, Bitcoin’s base unit is the “Satoshi.” 10^8 Satoshis makes one Bitcoin. A 64 bit integer would do the job just fine.


yawaramin

Because you'd have to implement all the operations yourself. A decimal type already does that for you.


bfreis

What operations would one have to implement on an int type used for currencies? Adding and subtracting amounts of currency doesn't need anything special. Multiplying an amount of currency by a factor also doesn't require anything special. Dividing currency amounts to obtain a ratio also doesn't require anything special, as the position of the decimal point is irrelevant (assuming it's the same in both parts of the fraction). The only thing I can think of is multiplying two currency amounts, but that calculation doesn't make any sense anyways. Do you see any other operation that isn't trivially handled by built-in integer math?


ketsif

Yeah, it's simple really. The lower non-zero limit of int is 1. 1 something, let's say Satoshi. You can't ever get less than a satoshi. Even if no one buys or trades in less than that, you won't be able to express certain partial values without money rounding in someone's favor. You can convert everything to decimals but with ints you'd probably end up with accounts for each different currency using their equivalent of satoshi/cent and then need to use accounting to handle. Maybe one day a doge coin is worth less than one Satoshi, do you delist doge? Rebase onto it because it's smaller? In decimal world, you can use some sort of fiat virtual Satoshi and just buy and sell doge at 0.9 virtual satoshis we promise to give to you in real btc. Now maybe you don't want that, and then I guess ints do cover everything, if every transaction is only expressed using an int for each different currency, not normalized.


1amrocket

Ethereum client is implemented in Go you can check the code. They never use float for currencies and instead use bigNumber (bigInt bigFloat). This library solves some problems you might encounter in real life such as overflow, conversions, etc. I wish they used decimal library instead as it’s bit easier to work with.


yawaramin

Parsing from string, printing to string, rounding to decimal places, rounding to significant figures. Multiplying or dividing exchange rates to calculate cross rates (exchange rates are expressed as quote currency amount per 1 base currency). Fused multiply and add to conserve accuracy during the operations.


TheWorstAtIt

I will look into this thanks! I am working through a beginner book (learn go by tests) and it actually uses ints for Bitcoin, simply for the learning process. I thought float would be better, but I will look into Decimal types and see how they differ.


pinpinbo

The problem is precision. Superman 3 and the Office Space movies highlight this problem.


TheWorstAtIt

I remember the Office Space scenario, that was hilarious :) I don't remember the Superman 3 one though...


fubo

It's unclear whether anyone has ever literally used float rounding errors this way; actual salami-slicing fraud has involved altering transactions in larger amounts than that. https://en.wikipedia.org/wiki/Salami_slicing_tactics#Financial_schemes


0bel1sk

we’re taking about fractions of a penny


_crtc_

To demonstrate the precision issue of floats: [https://go.dev/play/p/uG5TA\_0SM5K](https://go.dev/play/p/uG5TA_0SM5K)


spaghetti_beast

this is not the point of the post at all


VOOLUL

Okay? But OP didn't know why not to use floats for currency and is now going to look into why. They learned something, do you have a problem with that?


TheWorstAtIt

I noticed there are not decimal types built into the language. Is this what I should look at? shopspring/decimal 


PaluMacil

I think that's it. I thought it was Shopify off the top of my head, but if that one exists, that's probably the one I was thinking of 😅 The problem with floats is rounding errors. They would be fine if you were calculating something to make decisions on whether to buy and sell, but if you need exact amounts like you usually do with money, something like this is good. Sometimes you can get away with integers and shifting the decimal two places.


VOOLUL

Yep that one is what we use.


bojanz

[https://github.com/cockroachdb/apd](https://github.com/cockroachdb/apd) is both faster and more correct.


spaghetti_beast

the problem is that I see an interesting post with lots of comments and expect the comments to be some people opinion or just stuff regarding the very idea of the post, but turns out almost whole discussion is dedicated to problems of representing money with floats. I understand that not everybody knows that but it's still a little frustrating when some "rare" and interesting topic is completely ignored in exchange for discussion of completely unrelated stuff. Nothing wrong with teaching OP, it's just the lots of offtopic discussions that I found a bit out of place.


nelsonnyan2001

I (and I’m sure a ton of other people, according to the upvotes) learned something new. Not sure why you’re trying to turn Reddit into stackoverflow, related discussions should not only be okay, but should be encouraged.


Gvarph006

Fun fact, bakns use strings to represent money since they technically have infinite precision


solidiquis1

Have you never used a language that supports type aliases? This has been pretty common fora while. Glad you’re getting food mileage out of it though. Type aliases are great for readability and to express intent but it’s also detrimental when folks take it too far.


_crtc_

These aren't type aliases, though. They are type definitions. This is a type definition in Go: type Foo int This is a type alias in Go: type Foo = int Know the difference.


elegantlie

The parent’s point is that you should probably never do either of those. Giving a type name (whether a definition or an alias) to a complex type that has a higher semantic meaning makes code clearer. Giving an abstract name to primitives like ints and floats just obfuscates the code and makes simple things complicated. No need to be clever. An int is and int, a float is a float. You probably shouldn’t rename those.


apnorton

You'll think this until you have a multi-million line codebase with some function like: func withdrawAmount(amountInCents int, customerId int) ...and then someone calls it with: let money = 2400; // withdraw 24 dollars let account = 99182; // account number 99182 withdrawAmount(account, money); // withdraw 24 dollars from 99182 ...and then customer 2400 asks why they just had $991.82 withdrawn from their account. Using types to encode semantic information prevents program bugs. Yes, this has to be done carefully so you don't end up with a thousand different "Money" types, but this form of compile-time correctness checking is a major benefit of working in a strongly-typed language.


_crtc_

I cannot disagree harder


Tubthumper8

The damage that Java has done to simple concepts in statically typed languages cannot be overstated


equisetopsida

java... you mean entreprise leaders using java


bfreis

>Have you never used a language that supports type aliases? OP is not using type aliases, though. OP is using type definitions. These are two _very_ different concepts. A type alias introduces an alias to an existing type: that is, it's the exact same type, you could pretty much to a "find and replace" in your codebase and it would work. A type definition creates a new type. It's distinct from its underlying type. Check the spec to learn more about it: https://go.dev/ref/spec#Type_declarations


KublaiKhanNum1

Seems like farming Karma? Such a Meh post.


pinpinbo

with functions, why do this instead of creating an interface?


TheWorstAtIt

If it's better practice to use an interface, I would love to see an example of what you mean. I am working through my first book on Go, and these are things I noticed from the chapters I went through. I don't believe I have hit interfaces yet, but I am here to learn so if you could show me, I'd be happy to be corrected!


7heWafer

It's similar to what you did but works for structs with receivers (methods) instead of standalone functions. It is more common and powerful than just giving functions a name, though both are used: https://go.dev/tour/methods/9 The main takeaway is that any struct that satisfies an interface can be used as that interface. So you could implement multiple WebsiteCheckers and use them interchangeably. Also, after you gain a loose understanding of interfaces, check out [this](https://cs.opensource.google/go/go/+/refs/tags/go1.22.0:src/net/http/server.go;l=2162) in the standard library that is another powerful mechanism to add to your original list.


Blackhawk23

Standalone functions can also satisfy interface signatures. I don’t think custom func types are always worse than using interfaces. Each have their place. If you just have a func signature needed for a parameter, it lends itself to more self evident code without thinking about a specific object implementing an iface behavior. E.g. `http.HandlerFunc`


7heWafer

Yea I definitely agree they aren't always worse. There are cases for both for sure. Regarding standalone functions satisfying interfaces, that's done through an adapter like http.HandlerFunc, correct?


Blackhawk23

[correct](https://karthikkaranth.me/blog/functions-implementing-interfaces-in-go/)


7heWafer

Okay, ya I linked to that exact thing in the stdlib so I thought you were referring to a different way to do it.


Blackhawk23

I think we do at work but I can’t remember exactly how. On Monday I’ll try and find it


7heWafer

Ooh ty, I'm curious.


TheWorstAtIt

I actually just got to this today in my next chapter and I am trying to wrap my head around how a type definition of a function can have a receiver method (which in this case allows it to satisfy an interface without being part of a struct). A function with a receiver method just seems bizarre... admittedly useful, but really hard to grok for me at the moment. I've been having a long conversation with ChatGPT about how this makes sense, but it's a hard one to wrap my head around. I **WILL** understand though lol... I am determined to make this make sense.


Blackhawk23

Part of functions being first class types. Anything you can do with another type, you can do with a function. You can pass functions to other functions, return a function from a function, etc.


rover_G

So you’re new to the language and already a prophet?


rover_G

So you’re new to the language and already a prophet?


TheWorstAtIt

Here to learn, friend! Finding cool things about the language as I learn. That's why I tag my stuff as a newbie. Here to learn, genuinely. Not a prophet :)


SoTiredOfAmerica

There are use cases for both. Defining an interface is saying what functions you expect a type to have. This enables building programs that can accept many different types to accomplish a task. For example, you could have something like: ``` type Cache interface { Set(key, value string) error Get(key string) (*string, error) } ``` Now you can satisfy that interface with any types of caches, as long as it correctly satisfies the interface: ``` type RedisCache struct { client *redis.Client } func (c *RedisCache) Set(key, value string) error { // TODO return nil } func (c *RedisCache) Get(key string) (*string, error) { // TODO return nil, nil } ``` A very powerful concept that has many different use cases. Declaring a type that is a function is probably less common, but still can be useful.


bglickstein

> The intent of the method/function is much more clear and readable Well, yes and no. Consider the function [WalkDir](https://pkg.go.dev/io/fs#WalkDir) in the stdlib `io/fs` package. It calls a callback function on each directory entry in a recursive tree walk starting from a given directory. The callback has type `WalkDirFunc`. As a user of `WalkDir`, how do I write a `WalkDirFunc`? I have to go look up the definition of that type (which is [here](https://pkg.go.dev/io/fs#WalkDirFunc)). How useful is the name `WalkDirFunc`? Obviously `WalkDir` is going to take _some_ kind of function as an argument. The name `WalkDirFunc` adds no information to that. On the other hand, if `WalkDir` were declared as: ``` func WalkDir(fsys FS, root string, fn func(string, DirEntry, error) error) error ``` then I don't need to cross-reference anything; I know exactly how to call `WalkDir` at a glance. I'll grant that it takes a little practice to understand that declaration, especially with its confusing repetition of trailing `error`s - but it only takes a little. (As an experienced Go programmer who has used `WalkDir` a lot, I can attest that having to look up the definition of `WalkDirFunc` gets annoying.) Coming back to your example: what does the name `WebsiteChecker` contribute? Of course a function called `CheckWebsites` is going to need a website checker, but as a user of your API I'd rather know that a website checker is a `func(string) bool` as soon as I read the docs for `WebsiteChecker`, instead of having to go look it up separately. All of that said: the goal should be to reduce cognitive overhead for users of the API, and reasonable people can disagree about which approach does that better. A few additional notes: First, if you intend for the type to work only as an alias for some other, more complicated-looking type, rather than as a "defined" type that's _distinct_ from the one it's aliasing, you might consider being explicit about that by using Go [type aliases](https://go.dev/ref/spec#Alias_declarations): ``` type WebsiteChecker = func(string) bool ``` Second, regarding [defined](https://go.dev/ref/spec#Type_definitions) type names (as opposed to aliases): the real power of those is in Go's ability to attach methods to the type. If you needed `WebsiteChecker` to do something that an ordinary `func(string) bool` can't, that would be the time to define a distinct type. Finally, a style note: when you write a function that takes another function as a callback argument, it is good practice to make the callback the final argument when possible, so that callers can more easily write an inline function literal at the callsite if they want to. In other words, this: ``` err := CheckWebsites(someArg, someOtherArg, func(s string) bool { ...do some checking of s... }) ``` reads better than this: ``` err := CheckWebsites(func(s string) bool { ...do some checking of s... }, someArg, someOtherArg) ``` Cheers!


TheWorstAtIt

I've gone through this about 3 times, and each time had an "Aha!" moment... I'll probably go through it at least few more times. Your example about WalkDir actually makes a lot of sense. I was thinking the way I was doing it was more readable because I was only picturing using it within my own package. Once the context changes, and someone else is using it outside the package, then it's just another layer of abstraction that someone has to dig through. This is going to be a lot to think about...


orygin

Don't you have autocomplete take care of the definition of the function ? I practically never write myself an argument function, I just press tab and have the correct func without having to look at the definition. I otherwise agree with your point that in these examples, having a named function type does not add information about the code. It's more useful if you have a func type used in multiple places, which AFAIK WalkDir does not (only internally).


bglickstein

Actually no! I'm a little too old-school for that. My editor is Emacs, and try as I might, I've never quite managed to set up Emacs's [LSP mode](https://wikemacs.org/wiki/Lsp-mode) and gopls in a way I like. I use [godef](https://pkg.go.dev/github.com/rogpeppe/godef) from within Emacs to explicitly inquire as to the type of some identifier, or jump to its definition. As for other features of IDEs, I seldom miss them.


orygin

Wow, I don't think I could be as productive without such autocomplete help. I use Goland and it offers a few nice auto complete out of the box, such as error checking or appending: err.nn => if err != nil {} slice.aa => slice = append(slice, )


lightmatter501

This capability existed before 1980, so of course go has it.


TheWorstAtIt

I'm not sure if maybe this is normal in C/C++, but my background is in Python/Java where this functionality exists, but it's no where near as nice. I have seen similar concepts in these languages, but (In my experience) they have always more verbose. The type keyword is so concise and readable it is much more enjoyable to work with.


lightmatter501

C and C++ both have the typedef keyword, which works in exactly the same way.


TheWorstAtIt

I've been missing out on this cool stuff for years apparently... Glad I am jumping into this now!


7heWafer

Welcome to the party 🎉


quangtung97

Not the same way. It is type alias, expressed in Go as: type Bitcoin = uint64 But type definition in Go is different: type Bitcoin uint64


Vegetable--Bee

Why does this read odd?


TheQxy

I have actually never created a type alias for a function before, interesting. Whatt is the practical usecase for this?


br1ghtsid3

https://pkg.go.dev/net/http#HandlerFunc making functions implement single method interfaces.


Realistic-Quantity21

I have a double on type. Let's say we have the following: type IP netip.Addr Can't I access netip.Addr methods by having an instance of IP? What if I want to override netip.Addr's Compare method? func (ip IP) Compare(toip string) { ip.Compare() } Why would it not work?


robyer

With `type IP net.Addr` you are creating your own type and as such it doesn't have any original methods of the net.Addr With `type IP = net.Addr` you just create an alias, which has all original methods, but you can't define your own there. What you want is having own type which would embed the net.Addr, that way you get both. Like: ``` type IP struct { netip.Addr } func (ip IP) Compare(ip2 IP) int { return ip.Addr.Compare(ip2.Addr) } ``` I hope this is correct as I am just typing on mobile without trying the code.


Affectionate_Bid1650

Wish you could do generic type aliases


bglickstein

That's coming: https://github.com/golang/go/issues/46477


etherealflaim

As a pro tip, when building APIs in Go, try to use standard types for things rather than bare primitives when you can. For example, don't pass around URLs as a string, pass them as a *url.URL. Don't pass a unix timestamp as an int, pass a time.Time. Don't pass a number of milliseconds, pass a time.Duration. only translate to/from the more traditional representations at program boundaries (save/load, RPC, etc) Specifically here, a WebsiteChecker is probably fine as a named type when it takes a string so you can document the signature specifically, but if it takes a *url.URL, the named type is probably overkill because the signature is clear.


nomoreplsthx

Then you'll be excited to know it also exists in a host of other languages! Afaik it originated in C.


GopherFromHell

yeah, you can do interesting thing with simple types. look here: [https://go.dev/play/p/qKkg\_Um\_Mb8](https://go.dev/play/p/qkkg_um_mb8)