Comments by "Lawrence D’Oliveiro" (@lawrencedoliveiro9104) on "Computerphile"
channel.
-
1000
-
253
-
139
-
130
-
119
-
101
-
68
-
62
-
56
-
56
-
55
-
49
-
46
-
42
-
40
-
38
-
35
-
29
-
29
-
28
-
27
-
27
-
A couple of significant notational differences between C and Pascal are, when declaring variables, C inherits the Algol syntax of type-spec before variable name: “«type-spec» «var-name»” whereas Pascal invented its own syntax: “«var-name» : «type-spec»”. And also pointer dereferencing: C uses a prefix operator “*«pointer-val»” whereas Pascal has a postfix one: “«pointer-val»^”.
This may seem very tomayto-tomahto, but consider how C++, which inherits C syntax lock stock and barrel, is now trying to deal with newer features involving dependencies between types, for example between the arguments to a function and its result type. It now finds that, in addition to the C syntax of putting the return type before the function name, it also has to add an alternative syntax where the return type comes after the argument list, à la Pascal.
As for the pointer issue, this may not occur very often, but back in my Mac programming days, more than once I had to write an expression like “thePort^^.theGDevice^^.gdPMap^^.pmTable”,
which in C would have had to become “(**(**(**thePort).theGDevice).gdPMap).pmTable”. (These were structures defined for interfacing to the Mac system, not created by me.) Which would you prefer?
27
-
27
-
25
-
24
-
23
-
The three basic objects in Git are blobs, trees and commits. A blob represents the contents of a file, and is identified by the SHA-256 hash of its contents. A tree associates names with blobs, and represents a snapshot of source tree contents at a point in time. A commit holds a pointer to a tree, and to zero or more parent commits, as well as information about who did the commit and when, an explanatory comment etc.
Trees and commits are also represented by SHA-256 hashes of their contents. This is why a commit holds pointers to its parents, not to its children; because once the commit has been created, it cannot be changed (e.g. to add child pointers) without losing its original SHA-256 ID.
Branches and tags are just pointers to particular commits. They have no associated SHA-256 hashes, so they can be added, removed and changed at any time.
23
-
22
-
21
-
20
-
19
-
18
-
17
-
17
-
15
-
15
-
14
-
14
-
14
-
13
-
13
-
12
-
12
-
12
-
12
-
12
-
12
-
12
-
12
-
11
-
11
-
11
-
11
-
11
-
10
-
10
-
10
-
10
-
10
-
10
-
10
-
10
-
10
-
10
-
9
-
9
-
9
-
9
-
9
-
8
-
8
-
8
-
8
-
8
-
8
-
8
-
7
-
7
-
7
-
7
-
7
-
7
-
7
-
7
-
7
-
7
-
7
-
7
-
7
-
6
-
6
-
6
-
6
-
6
-
6
-
6
-
6
-
6
-
6
-
6
-
6
-
6
-
6
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
5
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
4
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
6:16 But then, if you are always filling rectangular areas rather than plotting individual points, then you will always be specifying the bounds of the areas to be filled. Thus, filling the whole playing field would mean filling a rectangle with corners (0, 0), and (width, height), not (0, 0) and (width - 1, height - 1).
In other words, stop thinking of pixels as little squares. You want squares, think in terms of squares, not pixels.
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
6:26 Not just an odd number, but a prime number: 7. One that cannot be evenly divided up into anything else!
Before byte addressability became de rigueur , computers had word lengths like 24 bits, 36 bits, 60 bits -- all numbers with lots of integer divisors including powers of 2 and 3, even 5, so they could be divided up into equal-sized portions in many different ways. But with byte addressability, the basic unit is 8 bits, and the natural machine word length (so far) is 2, 4 or 8 times this. So the only factors you have are powers of 2, which limits the ways you can divide them up.
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
3
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
Actually printf is an interesting case, because it shows up the drawbacks of trying to do everything as a library routine within the limited type system offered by C. The trouble is that printf needs to be polymorphic -- accept arguments of any mixture of different types -- which C really cannot handle. So it simply gives up and doesn’t bother checking the argument types.
There is another approach, which is illustrated by the memory-allocation API in Modula-2. The language defines built-in “NEW” and “DISPOSE” routines, which are fully type-safe, because they are constructs known to the compiler. But the language itself doesn’t define any memory-allocation routines. Instead, these constructs are translated to lower-level calls to routines named “ALLOCATE” and “FREE”, which are type-unsafe, and not defined anywhere in the language. Instead, they have to be provided somewhere in the program, or imported from some library. This way you get the best of both worlds -- type-checking at the high level, but full versatility of implementation at the low level.
Imagine if something similar could be done on a more general scale, for I/O and possibly for other things. C++ uses a similar idea for its memory-allocation mechanism, but unfortunately its “<<” and “>>” I/O operators are a bit rubbish. And its printf support is just as unsafe as in C.
GCC tries to do some type-checking of printf format strings. But this cannot work if the string is dynamic, since that requires the argument type information to be available at run-time.
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
It’s no coincidence that the sound is so similar to that made by telephone modems, since they are both trying to solve nearly identical problems, namely how to use a medium designed for audio to transfer digital data.
The obvious differences being:
* Modems can ask for a retransmit when they hit an error, whereas the cassette player cannot be stopped and rewound to replay a section under computer control (unlike the tape drives on the bigger computers of the time, for example).
* But on the other hand, even cheap audiocassette machines would be capable of better frequency response than the 4kHz standard bandwidth for telephone lines.
Another thing is, modem coding technology continued to progress a lot after the days of using audiocassettes for data, reaching a peak of a 33.6kb/s transfer rate in either direction before being eclipsed by broadband technologies like ADSL. If similar trellis-coding techniques were retrofitted onto cassette encoding, it should be possible to load a program of, say, 128kB size in a matter of seconds rather than minutes. Or would that take more processing power than an authentic 8-bit machine could manage?
And another thing to look at might be the incorporation of robust error-correction codes, like that on CDs and DVDs. But again, maybe this would require too much additional hardware or processing power to fit within the authentic 8-bit experience ...
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
2
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
Yup, that’s the right way to think of it. Say you have a function which evaluates a cubic Bézier curve through given control points p₀, p₁, p₂, p₃ at a point given by a parameter t. Instead of expressing it all as a single function
p ← B(p₀, p₁, p₂, p₃, t)
you split the function call into two stages
p ← B(p₀, p₁, p₂, p₃)(t)
so you specialize it once for particular control points
B’ ← B(p₀, p₁, p₂, p₃)
and then the specialized function can be evaluated repeatedly for the specified parameter values
p ← B’(t)
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
Try solving this without recursion: computing all the permutations of a sequence.
def permute(seq) :
"generator which yields successive permutations of the elements of seq."
if len(seq) == 0 :
yield ()
else :
for i in range(0, len(seq)) :
for rest in permute(seq[:i] + seq[i + 1:]) :
yield (seq[i],) + rest
#end for
#end for
#end if
#end permute
>>> list(permute((1, 2, 3, 4)))
[(1, 2, 3, 4), (1, 2, 4, 3), (1, 3, 2, 4), (1, 3, 4, 2), (1, 4, 2, 3), (1, 4, 3, 2), (2, 1, 3, 4), (2, 1, 4, 3), (2, 3, 1, 4), (2, 3, 4, 1), (2, 4, 1, 3), (2, 4, 3, 1), (3, 1, 2, 4), (3, 1, 4, 2), (3, 2, 1, 4), (3, 2, 4, 1), (3, 4, 1, 2), (3, 4, 2, 1), (4, 1, 2, 3), (4, 1, 3, 2), (4, 2, 1, 3), (4, 2, 3, 1), (4, 3, 1, 2), (4, 3, 2, 1)]
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1
-
1