Js is Weird in Ocaml
The web site jsisweird contains puzzling things from Javascript and runs a multiple choice test over them, to test if people understand the logic. Here, we do the same, but translate each of the questions into likewise OCaml:
Questions
Q1
# true + false;;
Error: This expression has type bool but an expression was expected of type int
First question is a type error. The +
operator isn’t defined for boolean values. The perhaps mathematically valid solution is to take +
to mean logical or, in which case the output should be equal to true. Javascript is what I tend to call “weakly typed” in that it does implicit type conversions. The true
is converted into a number, 1
, false
is converted into a number 0
, and then these are added up.
Q2
# List.length [;;;];;
Error: Syntax error
This isn’t even valid OCaml, because the list entries must have some value. You could use something like [();();()]
if you wanted. In JS, this outputs 3
.
Q3
# [1;2;3] + [4;5;6];;
Error: This expression has type 'a list but an expression was expected of type int
Invalid in OCaml. You can’t use a sum on lists. There’s another operator, which does the job:
# [1;2;3] @ [4;5;6];;
- : int list = [1; 2; 3; 4; 5; 6]
In languages with types, you often have specialized operators for different kinds of operations on different types. That way, you eliminate programming errors in the code, because the programmer has to explicitly ask for a given kind of operation when they need it.
Q4
# 0.1 +. 0.2 = 0.3;;
- : bool = false
First point where JS and OCaml will agree. The reason is that OCaml has a bug in the specification of floating point values because it allows them to be compared for equality. Standard ML doesn’t:
Poly/ML 5.7.1 Release
> 0.1 + 0.2 = 0.3;
poly: : error: Type error in function application.
Function: = : ''a * ''a -> bool
Argument: (0.1 + 0.2, 0.3) : real * real
Reason: Can't unify ''a to real (Requires equality type)
Found near 0.1 + 0.2 = 0.3
Comparisons of floating point values must use tolerances. You have to check if the two values are within each other by some small epsilon.
Q5
# 10,2;;
- : int * int = (10, 2)
OCaml returns a tuple. Javascript throws away the 10 and returns 2. This seems dangerous and I hope linters are good at catching this, because it’s a small error waiting to happen in the code base.
Q6
# not (not "");;
Error: This expression has type string but an expression was expected of type
bool
The Ocaml type system strikes again. This is not valid because you can’t invert the boolean value of an empty string. Javascript uses weak typing, implicit conversion and returns false
.
Q7
# +(not (not []));;
Error: This variant expression is expected to have type bool
The constructor [] does not belong to type bool
Again, OCaml’s type system to the rescue! This doesn’t make sense. In Javascript, +!![]
converts by first converting []
to it’s boolean representation, which is true
, then converting true
to a number due to the +
, which is 1
.
Q8
# not (not (not true));;
- : bool = false
Javascript and OCaml agrees here. It makes sense, since you are inverting an odd number of times.
Q9
# true = "true";;
Error: This expression has type string but an expression was expected of type
bool
You are not allowed to compare values from different types because normal equality is homogeneous. You can make systems with heterogenous equality, but this is most often used in the case where you have some evidence the types are really the same. You then present such evidence to the type system so it can verify it. It’s a more lenient form of equality, but it’s doesn’t allow you to cheat.
Javascript outputs false
. Because true
converts into 1
and "true"
converts into NaN
. After which falsity is obtained.
Q10
# 010 - 03;;
- : int = 7
# 0o10 - 0o3;;
- : int = 5
OCaml has a specific way of writing octal numbers, which doesn’t come into play unless you explicitly ask for it. Javascript chose the representation from C, where a preceding 0
yields an octal number.
Q11
# "" - - "";;
Error: This expression has type string but an expression was expected of type
int
OCaml uses the type system to eliminate nonsense, yet again.
Q12
# type null = Null;;
type null = Null
# Null + 0;;
Error: This expression has type null but an expression was expected of type int
OCaml has no null value, but we can create a null value as a new type. Clearly, the addition is ill-typed, so the compiler rejects this nonsense.
Q13
# 0 / 0;;
Exception: Division_by_zero.
utop # 0. /. 0.;;
- : float = nan
Typing it exactly like you do in Javascript, yields an exception because you are dividing by 0. In the case you request floating point values, the result is the same as in Javascript. It’s a consequence of IEEE 754 floating point numbers, where nan
represents an illegal number value. This can be useful in some situations, particularly when your data is incomplete, where you can use a nan
to represent lack of data.
It’s also useful for performance reasons. Run your computation to the end, then check for nan
. If present, the computation is failed and you can analyze why. This avoids the code to check for legality in each step, greatly improving performance for computations.
Q14
# 1/0 > 10**1000;;
Error: This expression has type int but an expression was expected of type
float
Hint: Did you mean `10.'?
utop # 1. /. 0. > 10. ** 1000.;;
- : bool = false
Again, like the Q13, this is a consequence of how IEEE 754 floating point values work. Both values convert to infinity
and they compare equal. However, in Javascript, floating point numbers is the default, whereas in OCaml you can opt to work with integers instead.
Q15
# true++;;
Error: Syntax error
Javascript and OCaml does the same here, thank god.
Q16
# "" - 1;;
Error: This expression has type string but an expression was expected of type
int
OCaml rejects this as ill-typed, where Javascript happily regards ""
as 0
, and then returns -1
. How can people work with this in practice?
Q17 + Q18 + Q19
All of these questions behave the same:
# (Null - 0) + "0", true + ("true" - 0), (not 5) + (not 5);;
Error: This expression has type null but an expression was expected of type int
OCaml only ever reports the first error encountered. But the other expressions are not well-typed either.
Q20
utop # [] + [];;
Error: This expression has type 'a list but an expression was expected of type
int
# [] @ [];;
- : 'a list = []
Same variant as Q3, and it should behave the same way. Javascript uses a concept of “primitive values” in which implicit conversion follows a set order. The first matching entry in this order is toString
, so the result is "" + ""
which is ""
.
Q21
# nan = nan;;
- : bool = false
This is a consequence of IEEE 754 floating point numbers. NaN values are never equal.
Q22
# nan +. 1.;;
- : float = nan
IEEE 754 again. NaN sort of represents the failed computation, so if part of the computation is a failure, then so is any further computation with that value.
Q23
# type undefined = Undef;;
type undefined = Undef
utop # Undef + false;;
Error: This expression has type undefined
but an expression was expected of type int
Another simple type error in OCaml.
Q24
# +0 = -0;;
- : bool = true
utop # +0. = -0.;;
- : bool = true
It’s important to note that in IEEE 754, the values +0.
and -0.
have different bit representations because the sign bit is flipped. This is why they can compare unequal in some cases. However, as a rule, you should never compare floating point values for equality.
Q25
# - "" + + "1" * Null - [,];;
Error: Syntax error
This has a value in Javascript! And it’s 0!
Some comments
The Javascript implicit conversion rules are complex. They are also dangerous. In some cases, where the programmer wants the program to fail, the implicit conversions make sure the Javascript code will continue to execute. In turn, the error will accumulate in the program.
In the lucky case, the error then makes some part of the system into nonsense and it breaks. Debugging will take time because you have to backtrack to where the fault really happened, but at least you have an error to work with.
In the unlucky case, the program will run. It will compute the wrong value. It will write the wrong value to disk. It will do this for several months and nobody will notice. Then all of a sudden, someone notices and you have months worth of data which were wrong.
In short: avoid Javascript for your important compute-kernels.