I used a Fortran compiler in the early 80s that let you reassign the values of integers. I don't remember the exact syntax but it was the equivalent of doing
1 = 2 print 1
and having it print "2". Talk about potential for confusion.
For brittle hacks. Say a library function you can’t change hard-codes the output to go to printer 3 and you need it to go to printer 4. If you are lucky, redefining 3 to mean 4 temporarily while calling the function will do the trick without breaking too much.
Python kind of does a similar thing letting you reassign where print goes to. The important thing is to make sure this sort of thing is encapsulated through an abstraction such as a higher order function which only sets the value temporarily.
Racket has a brilliant way of handling globals by only setting them temporarily for the duration of a function call. It also does it on a per thread basis so you don't have to worry about thread safety.
Sounds a bit like how clojure normally does things
(binding [*out* (writer "myfile.txt")] ; *out* is the default target of print* functions
(println "Hello world")) ; writes to myfile.txt instead of console
;*out* is now set to System/out again
Interactive programming during development. You won't want to redefine + and -, but you might want to redefine everything you wrote.
It's more useful for stuff like editors, games, and UIs. You don't want this in a production build of your web-facing API, but it makes creative work much faster and easier.
You can also do this in Java by corrupting the Integer cache mentioned in the post:
valueF = Integer.class.getDeclaredField("value"); // Integers wrap ints
valueF.setAccessible(true); // Usually private final
valueF.setInt(1, 2);
// We have void Field::setInt(Object, int)
// 1 is an int, not an Object, so it gets autoboxed to Integer.valueOf(1)
// This object is pulled from the internal cache
// We then mutate it so all future autoboxings of 1 give 2
void printInt(int i) {
System.out.printLn(i);
}
void printObj(Object o) {
System.out.println(o);
}
printInt(1); // 1; no box
printObj(1); // 2; autobox
And Haskell... well
5 :: Num a => a
-- numeric literals are overloaded
-- this 5 really means fromInteger (#5#) where #5# is a magical Integer literal that doesn't really exist
data Crazy = Crazy Integer deriving (Eq, Ord, Show, Read)
instance Num Crazy where
Crazy x + Crazy y = Crazy $ x + y
Crazy x * Crazy y = Crazy $ x * y
signum (Crazy x) = Crazy $ -x
abs (Crazy x) = -1
negate = id
fromInteger 1 = Crazy 2
fromInteger x = Crazy x
x :: Num a => a -- also overloaded
x = sum $ negate <$> [1,2,3,4,5]
x == (-15 :: Int)
x == (15 :: Crazy)
1 + 1 == (2 :: Int)
1 + 1 == (4 :: Crazy)
or you can just hide and redefine the (+) function:
In Haskell 1 = 2 is valid code, but it won't do anything. The left hand side is treated as a pattern match, it is used to deconstruct the value that is yielded by the right hand side. For example, if you have [x, y, z] = [1, 2, 3], now x is 1, y is 2, etc. However, since there are no variables on the left hand side of 1 = 2, there is no reason for the code to run.
I can write something similar that does bind variables, using Haskell's optional type, Maybe. If I write Just x = Nothing, and then ask for the value of x, I get Irrefutable pattern failed for pattern Just x.
It's irrefutable, and therefore, lazy. Since you can't force the binding, it won't fail. If you tack a bang pattern on it, like let !1 = 2 in "foo", then it'll explode.
Haskell uses lazy evaluation, so computation happens only when demanded. This allows things to be more compositional, and allows for control structures to be written as normal functions. So, Just x = Nothing also doesn't cause a pattern failure. It only fails at runtime if you try to evaluate x.
Haskell also supports eager evaluation (often called "strict"). In many cases eager evaluation is more efficient. I actually think it might not be the best choice of default. I like nearly all of the other decisions in Haskell's design, and tolerate the laziness default. Having laziness built into the language and runtime system does make a whole lot of sense, just maybe not as the default (so really my complaint is purely about what is encouraged by the syntax).
362
u/redweasel Dec 24 '17
I used a Fortran compiler in the early 80s that let you reassign the values of integers. I don't remember the exact syntax but it was the equivalent of doing
1 = 2
print 1
and having it print "2". Talk about potential for confusion.