Haskell or Rust are the same incidentally: while the same "lexical" operator is used all the time (`+`), numeric additions are defined for two values of the exact same concrete type e.g. in Haskell you can add two Int or to Float or two Integer, but not an Int and an Integer, or an Int and a Float:
Prelude> (1::Int) + (1::Int)
2
Prelude> (1::Integer) + (1::Integer)
2
Prelude> (1::Float) + (1::Float)
2.0
<interactive>:6:15: error:
• Couldn't match expected type ‘Float’ with actual type ‘Int’
• In the second argument of ‘(+)’, namely ‘(1 :: Int)’
In the expression: (1 :: Float) + (1 :: Int)
In an equation for ‘it’: it = (1 :: Float) + (1 :: Int)So in ghci:
Prelude> let x = 1 + 1.0
Prelude> :t x
x :: Fractional a => a
Prelude> x
2.0What I was intending to show occurred because I engineered the example to show it, yes, that was the entire point.
> Without the type annotations it would have worked just fine
Without the type annotation it would have been in an actual codebase rather than the REPL, and that codebase would have been typed.
> because the (+) function is polymorphic in the Num typeclass, and because literals are overloaded such that the literal 1 will have type Fractional a when type inference calls for it.
Which does not contradict my point at all.
> So in ghci:
Which works because (drumroll) both operands are of the same Fractional type. Which is the entire point of my comment: Haskell does not allow adding numbers of different types with one another any more than OCaml does.
Nor is it safe to allow implicit conversion in general, but F# shows that you don’t need separate operators for floating point. You just need a compiler that refuses to implicitly convert data types, as F# does, so you must use explicit conversions and type suffixes if you want to mix data types. If you manually convert, then on your head be the consequences.
This is not legal F#:
let x = 1.0 + 1
But both of these are legal: let x = 1.0 + 1.0
let y = 1.0 + float 1
F# doesn’t even let you mix single- and double-precision FP: let x = 1.0f + 1.0 // ILLEGAL
let x = 1.0f + float32 1 // legal let add x y = x + y
The proper solution for ad-hoc polymorphism is either dynamic dispatching (aka oop solution) or type-classes or OCaml's modular implicits, not ad-hoc monkey patches like that. And I find (+.) fine since float addition is not an arithmetic addition.If you don't call it, F# simply guesses "int -> int -> int". If you pass a float, you get back a curried function taking float and returning float. Now that the "add" function's type is known, you now can't pass an int: that's now known to be an error.
Seems sensible to me. No problem.
(+.) is the equivalent for floats: 1.0 +. 1.0
In short: if you need any operator for floats, and you know how to write it for ints, add a dot.
The reasoning is that it's hard to define the semantics of mixed expressions and explicit conversions clarify what needs to happen.
I've since grown to like it, because the only times I've been caught out by it is when I've made a mistake or I've forgotten I was working with floats.
Don't let the syntax put you off Ocaml, because there is some reasoning behind it. It's a multi-paradigm language that has a lot of powerful features.