jlouis' Ramblings

Musings on tech, software, and other things

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.