class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
"3.0" would be "Fractional a => a" which means it defines the functions in the typeclass Fractional (in addition to being a Num): class Num a => Fractional a where
(/) :: a -> a -> a
recip :: a -> a
fromRational :: Rational -> a
Depending on how you use the value, the type would be further refined in compile-time. For example, if you did `recip 3`, 3 couldn't be any Num anymore, it would have to be some Fractional.Regarding (5/3)*6, it does equal 10 using floating point. A better example would be how 0.1 + 0.2 is not equal to 0.3. We can see this:
ghci> 0.1 + 0.2 == (0.3 :: Float)
False
If we specified that we're working with Rational values, which are also Fractional a => a, and which are defined as a pair of integers, one representing a numerator and the other a denominator, roughly like so: type Rational = Ratio Integer
data Ratio a = a :% a
then we can see that 0.1 + 0.2 does equal to 0.3: ghci> 0.1 + 0.2 == (0.3 :: Rational)
True
It's pretty cool that Haskell lets you define new types of numbers and use them like any other. While you can transparently support hardware type numbers, represented by types like Int, Float, Double, Word, Word8, Word16, Word32, Word64, you also have transparent support for arbitrary precision Integer and Rational. Writing your functions to work with the typeclasses like Num and Fractional lets your functions work with any of these types and future types defined.I love all of the automatic casting between exact types, but happily implicitly casting to inexact types is (in my opinion) a big mistake.
What do you mean? As far as I'm aware, you always have to explicitly convert exact types to inexact types in Haskell (using "fromIntegral" and "realToFrac").
> Coupled with the fact that so many standard functions (like length, for example) want to return an Int rather than an Integer, it just leads to me spewing ((fromIntegral ___)::Integer) all over my code.
Indeed this is rather annoying. It's this way because of legacy reasons (see https://www.reddit.com/r/haskell/comments/60u10b/do_function...).
> I love all of the automatic casting between exact types, but happily implicitly casting to inexact types is (in my opinion) a big mistake.
> I was arguing that a cast from Num to Float, Double, Int etc is a lossy operation, and breaks the semantics of arithmetic, and so should not be done implicitly
It's not really casting; it's just type inference. You can't have a Num and use it as a Rational in one place and a Float in another (unless you lift the monomorphism restriction), and you can't add a Rational and a Float without explicitly casting one to the other by the use of a function like fromRational. Like so, real casting is very much explicit. I feel like you're technically arguing against representing Floats with decimal numbers in code, but I don't think you really mean that.
EDIT: At the risk of stating something that you might already know, (x :: Int) in Haskell is not a cast like (int)x in C. (x :: Int) simply further restricts the list of possible concrete types that x could be. If the code context implies that x is a Num a => a, that means it could one of an Int, a Float, etc. Doing (x :: Int) simply says to restrict those possibilities to only Int. If x were a Float, and you did x :: Int, that'd be a type error because it was never possible for it to be an Int, only a Float. You'd need to cast by using a function like floor, ceiling, or round.
EDIT 2: To further explain this, C's (int)x is a run-time operation that converts whatever x is to an int, while Haskell's x :: Int is a compile-time operation that states that x can only ever be an Int and never something else. At the end of compiling, Haskell needs everything to be concrete types. Num a => a is not concrete because it can be many types and if Haskell can't resolve it to a single concrete type implicitly by context or explicitly by signatures like x :: Int, then it has to resort to defaulting rules or raise a compile-time error informing the programmer of the type ambiguity.
EDIT 3: On:
> Coupled with the fact that so many standard functions (like length, for example) want to return an Int rather than an Integer, it just leads to me spewing ((fromIntegral ___)::Integer) all over my code.
I agree it would be nice for length to return a Num a => a instead of an Int, but it's nice that the reason is to maintain stability and limit code breakage from the times before Num existed. The Haskell community seems to really care about that. They did add genericLength which does return a Num a => a, though. There's other generic functions added to Data.List.
As a long-time Racket and Scheme user, I'd say that arbitrary precision numbers as default bring more disadvantages than advantages. As an option with syntax support Yes, but not as a default. it just makes it harder to port all kinds of code that relies on modulo arithmetics.
If you're relying on modulo arithmetic, you should be using a fixed size integer anyways, not one that varies based on the architecture. Go has fixed size integer types available, and modulo arithmetic should be using those, not "int".
Rationals aren't supported natively by processors, so there's no real need to handle this as a primitive instead of letting people to use a library for it. Adding them to the base language just because some people would find it convenient would clash with Go's explicit minimalist philosophy.
It's generally nice when you can do simple things with the language's built-in standard library. I tend to prefer languages with more powerful standard libraries because it means that you can more easily move across codebases since they'll all be the same. If commonly used data types like rationals, hashes, maps, strings, etc., aren't defined in the stdlib, then there'll be any number of different libraries to use them, and you'll have to potentially re-learn a lot of different unnecessary things when working on different codebases.
I'd say that would fit in quite well with the brand of minimalism that Go is representing, keep the language simple and provide builtins where more convenience is needed. The opposite kind of "minimalism" would be providing tools to allow libraries to define what other languages can only support as special builtins. C++ and Scala went down that road, they minimize builtins by empowering libraries to replace them. That's also a form of minimalism, but not the one chosen by Go.
Tangent: now I wonder wether Scala has inerited special handling for the (not special, but specially handled) string type it inherited from Java or if just reimplements those syntax extras in the standard library using implicits.
Scala uses Java's String exactly, but does define a few extra methods using its extension method functionality (called implicit class in Scala)
Templates like s"This is a string with a $variable", are part of the language, but `s` is a stdlib feature.
xxx"First $a $b Second"
will be translated by the compiler to new StringContext(Array("First ", " ", " Second")).xxx(a, b)
The standard library defines `StringContext.s`, but there's no restriction. Several SQL Libraries for example define `StringContext.sql`, to allow you to safely and type-checked embed SQL queries directly inside the code.Rationals are also the default number type.
Lists are the base type used throughout Scheme, and can be used to implement arrays and hashmaps and whatnot, so Scheme doesn't really need them, you can just use a library.
... That didn't stop the committee adding types to the spec. Like records, promises and other complex types.
What is useful and necessary to a language isn't defined by what the processor can natively do, or we'd only use registers, not arrays. And it isn't defined by a overbearing attitude towards one or more philosophies.
Probably a good linter idea in there somewhere.
However, having made the mistake a few times now of trying to use "unsigned ints" for things that I want to assert are never negative, I've learned the hard way not to do that. The problem is, there's always some bug you have in the program that will drive the uint "negative", wrapping over to a huge number. Unfortunately, since you get no warning when that happens, it tends to take longer to show up, and without a clear cutoff to say "ah, this is clearly invalid" it can be hard to even program detection code, whereas checking for "less than 0" can be unambiguous.
I'd loooove it if I could easily, cheaply, and universally (i.e., not just in Go, but across all my programming languages) turn on a behavior that says "if this int or uint under or overflows, throw an exception instead of trying to do whatever stupid thing you're going to do". I get the sense this is heading down the road where in 20 or 30 years, this is just going to be common sense. However, we are very early on the road yet from what I can see. Whenever I bring it up, even today, even in this sort of context, it still is generally negatively received. (But not uniformly. A few other people agree. I think momentum is with me. I'm not sure my programming career will live long enough to see it, though.)
Overflow flags are supported on some architectures, so it would really just be a matter of checking that flag.
> For signed integers, the operations +, -, , /, and << may legally overflow and the resulting value exists and is deterministically defined by …*
It does not go on to define the overflow values; that gets to be implementation defined.
This is in contrast to unsigned integers, which the spec does precisely define overflow for.
If x is unsigned, then you can’t rearrange an inequality like x >= 4 into x - 4 >= 0, since the first inequality is only true sometimes, whereas the second is true always. This looks obvious, but bury it a little way into some nontrivial index arithmetic and boom.
Another more aesthetic reason is that indices can represent offsets into a structure relative to some other index, and in this case you really need negative numbers.
What would be a more compelling argument is to use int in the same way that python does - a negative accessor means offset from the end of the array, not the start. I'm not sure golang lets you do this though...?
I want to emphasize that I do not know the true answer to this question.
I think that indexes are int to play nicely with a decrementing loop counter. Doing:
for i := len(n) - 1; i >= 0; i-- { fmt.Println(n[i]) }
requires i to be signed due to the last iteration. for(size_t i = zn; i-- > 0 ;) printf("%s\n",n[i]);
(This is a large part of why unsigned over/underflow is well-defined behavior in C.)More generally, assignment is always a statement, and never an expression (e.g. you can't say a = b = c)
A modern language with this type of confusing and platform dependent behavior is inexcusable. By all means, include a native data type as big as the processor can handle (size_t), but don't call it int which is the default data type most people will use out of pure laziness.
Defaulting to 64 bit math on 32 bit systems will have a huge performance penalty on generally the slowest/oldest systems where this penalty is least desirable. It's not clear to me what the benefit to this would be for most programs.
Running 32 bit ints on 64 bit systems will cause problems handling large arrays on systems with a lot of memory. I think it also complicates generating efficient loop code, although C++ gets around this with signed integer overflow being "undefined behavior".
Of course Go has int64 and int32 types if you want to use them (and all conversions require explicit casting), but the default type for array lengths etc is the platform native type. But what is the better alternative?
Unfortunately I've read every comment in this HN thread (so far) and I haven't found any specific one mentioned, just people calling it "crazy" and "confusing" over and over.
Go is a language that has explicit pointers! And of course those can be 32 or 64 bits and change struct sizes, field offsets etc. There are certainly things about Go that are confusing but I've never thought this was one of them.
Just FYI Go has int8, int16, int32, int64, and int types. Only the 'int' type is the machine-native one. It's always been obvious to me that 'int' is the one without a fixed size, but I think this would be less obvious if the naming convention was different using short, int, long etc.
I don't know how many users are still on 32bit. Maybe low power network devices? Older mobile phones (Go isn't usually run on mobile, but it's possible...).
If at some point in the future 32 bit systems become totally irrelevant for Go users, maybe a release of Go could drop support for it altogether and make int an alias for int64, and save people some type casts. 32 bit systems probably aren't at that point yet though.
Well, 16 bit would also be possible if someone took the effort. Perhaps it just shouldn't be encouraged.
But I still think that native types have their place. It can often be quite reasonable to accept serious limitations on narrow machines where the performance gains won by the trade-off are desperately needed, lift those limitations on wider machines and grant an enormous safety margin on the widest architectures. When you dive deeper, the values where this makes sense still tend to be "index-like", as GP stated, but they don't have to strictly be indexes (e.g. IDs, a wider machine will be capable of working on bigger datasets and therefore be more likely to exhaust a given width of IDs, all indexes are identifiers but not all identifiers are indexes)
My C is a bit rusty but why should #defines be used in this scenario? A typedef set in a config.h.in is more than enough to meet the requirements of this usecase.
As for the int vs int64, I definitely run into what I describe on Project Euler. Probably using a self referential map[int]int when I need to suddenly pull an int64 and everything gets infected.
I realize it's just a matter of convenience for me, but I personally have no downside to int == int64.
Take averaging as a very simple example. Doing (a + b) / 2 will overflow if a and b are sufficiently large, even if the average will always fit in 32 bits. Things like this go unseen for years.
If most people make mistakes, they should self-select to languages which have properties that protect them from their ignorance. for example, I mostly program in Python using longs so I don't have to worry about overflow.
Maybe people ought to self-select, but that would mean they'd need the training or experience to recognize what they don't know. It's often the most ignorant people that believe they have the most expertise.
I typically wouldn't be passing around milliseconds since epoch as an raw number, but then that's C#, I guess you might do that in other languages for performance or out of necessity.
My main issues with it are:
1. Like many other performance optimizations, it is a trade-off. You may be sacrificing easy maintenance or even introducing breakage (if the person using it doesn't understand the limitations and implications). So IMHO you should only do it if you can show through profiling, etc. that the gain is real and worth it.
2. As a matter of language ergonomics, it should never be something that anyone does unknowingly. You should have to explicitly ask for it. The type name should be something blindingly obviously like "native_int" or "word_int".
On CPUs with 64-bit and 32-bit registers, that typically is the register-sized integer type. Machines with 16-bit registers are a bit of an edge case, as 16-bit integers may both be a lot faster on them then 32-bit integers and, in many cases, too small)
If go chose to use 32-bit integers as default, some users on 64-bit systems would complain that they couldn’t create, say, an array of 6 billion ints. If they chose 64-bit, some users on 32-bit systems would complain their loops were needlessly slow, and code bloated (why waste 4 bytes of almost every integer in a program?)
Not having generics makes this more of a problem. If go had generics, switching between integer sizes could be a matter of recompilation using a different compiler flag)
A sufficiently advanced compiler would help here, but go doesn’t have one (not exceptional), and doesn’t even aim to have one (compilation speed seems more of a goal than run-time speed)
Isn't it already the case that you can switch between int sizes with compiler flags?
var i int
That will be 32 bits if I compile it with GOOS=linux GOARCH=386 and will be 32 bits if I swap in amd64 instead of 386 there.Is there any language where generics are useful for switching between int sizes? None come to mind offhand, so I don't quite understand what you mean.
For comparison, Java's int type is 32 bits on 64 bit systems, but then this limits the array size. It seems like a lesser evil to have a platform-native int type than to limit the size of arrays (especially since servers can be expected to increase their max memory going forward).
Of course, standardizing on 64 bit ints instead would require emulating them on 32 bit systems, when they can't support that much memory anyway (you could argue that 32 bit systems don't matter much anymore, but if you really don't care about ever running on them, then you can just treat 'int' as 64 bits and not variable size).
If you really need to, it's easy to wrap this into your own BigArray-class holding a 32bit-array-of-32bit-arrays and overriding the []-operator with Int64 as argument.
Take for example code which finds consecutive differences in a list, something like
for i := 0; i < len(list) - 1; i++ {
print(list[i+1] - list[i])
}
This code looks fine and reasonable, and we even avoided the off-by-one error at the end. However, if len(list) is a uint and we run this on an empty list, len(list) - 1 is then 2^32 or something, and the program explodes.So this is the rationale for using signed integers rather than unsigned integers. I think the language semantics should go further, and use true integers pretty much everywhere. Note that the compiler could still easily see that the index variable in the loop above is bounded, and use a machine word under-the-hood. But the programmer only ever has to think about well-behaved integers.
uintptr an unsigned integer large enough to store the uninterpreted bits of a pointer value
https://golang.org/ref/spec#Numeric_typesThe original thread was about converting int to be an arbitrary precision type: "so that 'int' can become a true integer". One would imagine that, to preserve the ability to do pointer <-> non-pointer number, they would just leave uintptr alone, and so basically ignore your concerns, whereas int as the "native type" for slice indexes and the like is a more likely reason to prevent messing with "int" itself
But it's still plenty useful, because it allows to accurately represent decimal fractional numbers, which is exactly what's needed in many domains, since humans work with decimal fractional numbers. Representing money would be one particular example.
(And yes, I know that Conrad Zuse invented floating point over a century ago ;))
Having said that, I'm by no means a Golang expert – this is the comment of an outsider looking in. I get that the proposer is Rob Pike and obviously Mr. Pike is god-like so what is it I am missing? Is Go 2 meant to break everything? That's like, wow.
Any low-level hardware bit-banging code is going to have to be audited or break in subtle ways, will it not? Is Golang different from C and C++ and languages of that ilk that this sort of change is not that much of a problem? Help me out here folks, I'm genuinely perplexed.
> so what is it I am missing?
The Go team has been adamant about avoiding any changes that break existing code. The whole point of Go 2 is that at some point that might have to happen and they're trying to minimize that pain.
Admittedly, not for things I build in my day-job but mostly for the kind of things you'd get when doing 'project euler' or 'leetcode'. Or more seasonal, the coming "Advent of Code".
It is bizarre to me that languages boasting built-in language-level data structures like lists, hashtables, etc are content to just leave us with the bare minimum support of numbers, being basically whatever the hardware thinks a number is. The semantics of integers and fractions are perfect, and everybody already knows them. On the other hand, overflows in int32’s are weird, and if your idea of a fraction is a floating-point number, then you can never have something like (5/3)*6 evaluate to 10 exactly.
To be clear, I think fixed-width integers and floating-point numbers have their place, I just see no reason why they should be the default.