2

All languages I have seen so far have multiple builtin data types (int, double, float, char, long...).

But if we look closely, they are just arbitrary arrays of bits, the only difference between them are their methods (addition, dividing, toString, etc.).

So why did all those programming languages decided to provide multiple built-in data types instead of built-in methods and only one built in data type - either bit or byte?

Example:

def newtype  int8 = bit[8]
def function +(int8 x, int8 y) = built_in_add_for_int(x, y)
  • good one, @gnat. your duplicate is another duplicate and you've marked it yourself :-) – miraculixx Jul 16 '16 at 12:51
  • duplicates are not always transitive – gnat Jul 16 '16 at 12:53
  • 2
    Many of "all those programming languages" modeled their data types on what was natively supported in hardware. – dbasnett Jul 16 '16 at 13:19
  • 2
    None of those types are necessary; in fact, types are not necessary at all. We can compute any mathematical function that is computable in one of those programming languages without even using bits in the untyped lambda calculus. Why on earth would you use a programming language with all these unnecessary concepts like bits, numbers, true, false, and so on, when you can develop them all from scratch using Church numerals? – Eric Lippert Jul 18 '16 at 17:18
8

Let's forget for the moment that processors have specific hardware for manipulating byte sequences of a particular size. Let's forget for the moment that processors have specific hardware for operating on specific interpretations of byte sequences (floating-point registers, SEE registers, etc).

What good does this abstraction do from a user perspective? After all, programming language exist for the convenience of the programmer. If the abstraction isn't making something convenient for the programmer, then the abstraction may as well not be there.

So the only fundamental type the language provides is a byte. And therefore every other type is some form of byte sequence. OK.

We don't want to have different programmers declaring different forms of the same types like float or int32 or whatever. After all, one person's 4-byte float wouldn't be interoperable with someone else's 4-byte float. Not only that, different programmers would be wasting lots of time implementing the same thing.

So the standard library for such a language would have no choice but to include generally useful sequences and operations on them. There would be some equivalent of float, double, int, etc.

So, what exactly have you gained compared to having these be language-supplied types? You still have to provide them, either way.


Programming languages (that aren't assembly, and even assembly to some degree) exist to provide useful abstractions to the user. They allow the user to program for an abstract model rather than the specific vagaries of a system, thus allowing the compiler to do the mechanical work of translating that abstract model to a real system.

However, an abstract model can sometimes get in the way of optimal transformations. Sometimes, it is because the abstract model defines too much. For example, Java defines pretty much everything about its basic types. float is guaranteed to be a IEEE-754 32-bit floating point value. Which means that if you want to run a Java program on a platform that doesn't have IEEE-754 support in hardware, you must software-emulate all floating point math.

By contrast, C/C++ says nothing about what float provides other than the fact that it can have values that aren't whole numbers. As such, a compiler for hardware that has no IEEE-754 support could convert float operations into a form of fixed-point math. Or do something else unusual, but something faster than software emulation of IEEE-754 floats.

Your abstract model fails in the other direction. It specifies so little that it's actually under-specified compared to the hardware, thus making conversion into hardware operations harder. Hardware can act at the byte level, but actual CPUs operate at the register level: groups of bytes with some particular meaning. There are even different kinds of registers with different kinds of operations on them.

Consider what happens if you had your bit-array system that's based on 8-bit bytes, and then you take it to a system where the CPU uses 9-bit bytes (and yes, that's a real thing). C and C++ support such systems, precisely because they don't specify things at the bit level. An int might be a 4-byte object, but it would be 36 bits in size. Your int32 would be useless on such a system, as the hardware can't effectively support it.

Finding the right abstraction for high-performance, one that's high enough to be a useful abstraction for the programmer, high enough to cover lots of hardware, yet low enough to not get in the way of the programmer, is a very difficult job.

  • Good answer. Also, imagine the amount of glue code that would be needed for a programmer to use just two externally written libraries! – James Youngman Jul 17 '16 at 9:04
  • Not to mention that introducing arithmetic as a software abstraction over logic will cost an average of 10-100 time machine instruction, thus making every program 10-100 times slower. All that when arithmetic can be implemented in hardware since 1970 using the same logic gates of actual processor. (and even before, if we consider discrete transistors on electron tubes ...) – Emilio Garavaglia Jul 17 '16 at 21:33
  • 1
    "C and C++ support such systems, precisely because they don't specify things at the bit level." -- also note that it's not so much that C wouldn't support those systems if it did have a bits-per-byte dependency like that: C was originally designed for the PDP-7 which was an 18-bit machine, so it wouldn't work on modern computers if that were a problem for it. – Jules Jul 17 '16 at 21:49
  • 1
    @Jules Unix and B (A typeless language that basically does exactly what this question is asking, though using words rather than bits or bytes) started on the PDP-7, C happened after the move to the 16 bit PDP-11. – 8bittree Jul 18 '16 at 13:46
11

Because processors have operations specifically for ints, and specifically for floats. The compiler has to know what operation to target.

And I mean, even if you had adding for a series of bits, adding 0110 and 0001 have very different meanings if 0001 is treated as a float rather than an int - not to mention actual operations required to calculate it.

And in your example code, you still have ints... You just added extra steps to make one.

8

So why did all those programming languages decided to provide multiple built-in data types

Because the built-in types like int, float, byte and char are used in almost all use cases – and it turns out that standards are a convenience for everyone.

If everyone used their own variant, writing code that uses libraries with different types, exchanging data between programs etc. would all be virtually impossible, or at least extremely cumbersome and error prone.

As a result, programmers would spend a lot of time researching bugs due to incompatibilities on the very basic level of datatypes, instead of solving the actual problem at hand.

But if we look closely, they are just arbitrary arrays of bits,

Actually, if you look closely that's not true. They are very specific arrays of bits, sometimes with very special meaning:

  • int comes in signed and unsigned interpretation, the most significant bit in signed interpretation indicates + or -

  • float, double is usually an implementation of the IEEE 754 standard on floating point numbers. not arbitrary at all

  • char strings of bytes carry meaning in some encoding like Ascii, UTF8 or Unicode

  • byte, int, long make guarantees about the range of values they can hold

So having these commmon, well-defined built-in datatypes is a good thing.

3

There have been languages that, as you suggest, provide only a single datatype and then have operations where the expected encoding of the data is provided as part of the operation, rather than being determined by the type of the variable as it is in most modern languages. The best known of these was probably B, the predecessor of C (in fact, C can be thought of as 'B with variable types', at least loosely). B had only one datatype - a machine word (which, on the machine it was designed for, was 18 bits... note that our modern obsession for multiples of 8 bits has not always been universal!) which could be interpreted as either an integer or a pointer depending on how you used it (it didn't support floating point at all).

The reason that we don't see many of those any more is that they are difficult to work with, and adding additional types is (1) very simple and (2) makes code easier to read as well as write. I believe the last well-known program that was written in B was AberMUD in 1987 (its primary author switched to working with C soon thereafter, and went on to become one of the best known contributors to the Linux kernel).

  • And, of course, BCPL. I've programmed in it, it's a pain. – Nick Keighley Jul 21 '17 at 7:59
1

You are correct that numeric types are just patterns of bits. But processors are optimized for certain operations on certain patterns of bits. For example x86 processors have registers of sizes 8, 16 and 32 bit, arithmetic operators corresponding to these sizes, and memory is addressed in chunks of 8 bits, known as bytes. There is no support for 19-bit addition for example. Of course it could be supported by the compiler but then transformed into 32 bit addition on the machine code level - but there would be no point.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.