If ReasonML is able to form a real community, I have high hopes for its long-term prospects. Such an enjoyable language to use! I think their general approach of bootstrapping a community by lowering impedance with the JS ecosystem is a decent one.
In case anyone on the OCaml team is reading this though, there are two language-level changes that I think could do wonders for wider adoption. The first is modular implicits: it feels so kludgy to have to type `a + b` for integers, `a +. b` for floats and `a ^ b` for string concatenation. I know it sounds like a small thing, but it makes the language feel inelegant, and aesthetics are important. The second is a good concurrency story, ideally one that is compatible with JS-style async/await keywords for easy interop.
I know those are both being worked on; I just hope they become available in time to coincide with the wave of interest in ReasonML!
> The first is modular implicits: it feels so kludgy to have to type `a + b` for integers, `a +. b` for floats and `a ^ b` for string concatenation. I know it sounds like a small thing, but it makes the language feel inelegant, and aesthetics are important.
That's interesting, as being forced to use the same operator for addition and for concatenation (and what's more with the result of mixed types if allowed often dependent on the order of the parameters) has always seemed extremely inelegant to me.
Addition and concatenation are not the same thing. Why they should share a symbol when they don't share some properties integral to their nature (it's non-commutative) is beyond me, and I think it's caused a lot of bugs that didn't need to happen.
Not allowing the addition of floats and ints together is less of a fundamental issue, but it also helps quite a few problems. Al alternative solution, if you didn't want to reply on implicit coercion would be to explicitly convert one operand to the appropriate type so they match.
Or you can go whole hog and provide an entirely separate set of operators for different types, like Perl did (string concatenation is '.', and equivalents comparitors are eq,ne,gt,lt,ge,le). That works well in Perl's case, but that's mostly because for Scalars Perl really just wants to know whether you are treating it as a number or a string, so there's only two types to account for.
Couldn’t the type system still allow using the + operator, but requiring the values on both sides to have the same type? You could do more magic like having int + float return a float, but I’d even prefer typing “int.toFloat + float” than “int +. float”.
Yeah, that's what I was referring to by explicitly converting one operand so they are like types. I too would prefer this in a language that takes typing of this sort seriously.
> Not allowing the addition of floats and ints together is less of a fundamental issue, but it also helps quite a few problems.
Needing to use +. to add two floats seems odd to me as well, and I believe this is what OP was pointing out. I would agree with you that adding and int to a float should require conversion of one of the arguments, but I see no need for a separate + operator for floats and ints.
> I would agree with you that adding and int to a float should require conversion of one of the arguments, but I see no need for a separate + operator for floats and ints.
Yes, what I was trying to express is that in a typed language with many types, choosing a few types and using operators to signify what you are doing is an odd choice, especially if you implement it for some types but not others. I understand the want of the language designers to protect people from these mistakes, I just think they chose an oddly inconsistent way of of doing so. The logical conclusion of different operators for different types is many more operators than shown here, so that leaves the language with these special cases. Special cases cause confusion.
Isn't it also the case that floating point addition may not necessarily be commutative? I'd imagine there would need to be some standard to enforce it as such.
No, addition is still commutative (i.e. a + b == b + a)
Sadly, it is not associative (i.e. (a+b)+c != a+(b+c))
Imagine a being a large number, and b and c being so small that adding each single one to a will result in a, but adding their sum will result in just the smallest increment over a.
Actually, floating point addition isn't necessarily commutative[1][2], which is one of the problems of thinking of floating point as just a number. Although in this case, it's at least still addition, but the type of things being added have special conditions.
It's more like doing calculations on scientific notation, where there are special rules for how to keep the right number of significant figures/precision after operations. Like scientific notation, you would probably convert to one form or another (even if in your head) before doing the operation, and keep note of the possible loss of precision.
This is exactly the reason why adding floats to ints is something that people think should require an explicit conversion, so it's more obvious that's a point that may introduce loss of accuracy.
Urgh. C semantics. Well, at least that will not happen if you actually use IEEE754 _and_ if a, b, and c all have the same floating point precision _and_ there is no weird promotion/precision change going on.
You might like to try Fable and F#. I’ve been looking at it since playing with Reason and Bucklescript, and so far I’ve found Fable much easier to use and more featureful. The syntax is essentially the same as OCaml, plus it’s whitespace aware.
F# has implicit `+` for basic types (though it’s not quite modular implicits). It also has a really nice syntax for async computations, similar to `async/await`. JS interop is much easier too. It can also run on the server with .NET core if you don’t want to use node.
The output JS is slighter heavier and less optimised than Reason/Bucklescript, and compile times slighter slower, but at the moment those are trade offs I’m happy to make
I'll second the recommendation for F# and Fable. I'm currently working on my first project with it, where both the client and the server run F# and can share code/types. I haven't been this happy with a stack in a long while. My one complaint is how... salty some members of the F# community can be, mostly regarding the rest of the Microsoft-backed .NET ecosystem.
The "+" in F# is really awkward because it tries catering to both the ML type system it got and to the .NET type system underneath, inherited from C#.
This is a problem because when type inference kicks in for plain functions, usage of that "+" will make the compiler assume "int".
But worse, "+" in C# is not a method, but a static method and the compiler basically does magic, because you don't have this behavior for plain static methods and you don't have a generic interface in the language for "+", there's no Number, no Monoid and static methods themselves in C# can't be polymorphic.
The whole notion of "static methods" is broken btw and have nothing to do with OOP. The only purpose of static methods is to enforce visibility rules and this made some sense in C++, where you could have functions living outside of classes, but even there the real problem is the lack of useful namespacing.
And F# inherited this behavior, F# inherited static methods, along with math operators described as static methods.
So in order to abstract over things that have "+", which is really needed in F# otherwise you can't express really basic things for an FP language like "List.sum", you have to (1) use inline functions, which aren't translated directly to .NET functions and (2) use a really awkward signature that instructs the type system that the type has a "+" static method that it can use, e.g...
> let sum x y = x + y;;
val sum : x:int -> y:int -> int
> let inline sum x y = x + y;;
val inline sum :
x: ^a -> y: ^b -> ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
So this signature is really interesting — the type system is saying that our "sum" needs either an A type or a B type and one of them should have a static "+" defined, doesn't matter which one.
This is close to how type classes work in other languages, however for the F# compiler this is magic that doesn't scale and making use of "static methods", a flawed concept, a signature that can't be encoded in .NET's type system and therefore the function is forced to be "inline".
Which is another problem, because in F# inline functions are more like macros, or in other words these "inline" functions are not values. As soon you try using one as a value, it gets materialized and you loose the type info. So it is very ephemeral.
Given this, frankly, I would rather have OCaml's operators.
At least they are plain functions.
Smalltalk is not a statically typed, nominally typed language, Smalltalk is dynamic and in Smalltalk classes are objects themselves, so any comparison to Java, C# or F# does not apply. This is relevant in the context of OOP because in OOP when you talk of methods, you talk of single-dispatching, polymorphism and the Liskov substitution principle.
Static methods are just plain functions tied to classes from an era when people thought having classes everywhere is actually a good idea, in C# and F# also tied to weird, magical compiler rules around operators.
Speaking of Smalltalk and OOP, operators like "+" in languages like Smalltalk are object methods and can be used in generic functions doing polymorphic calls. In OCaml they are plain functions with no overloading. In Haskell they are functions requiring type classes to work, sort of like what F# is doing, but non-broken.
Btw, if you're wondering from where the convention of using static methods for operators comes from, that's another relic of C++, a language not known for elegance, finesse or for being a good OOP language.
> This is relevant in the context of OOP because in OOP when you talk of methods, you talk of single-dispatching, polymorphism and the Liskov substitution principle.
Which also apply to Smalltalk.
> Static methods are just plain functions tied to classes from an era when people thought having classes everywhere is actually a good idea,
Using GNU Smalltalk serialization syntax,
Object subclass: #MyClass.
MyClass class extend [
myClassMethod [
^ 'Hello from class method'
]
]
Transcript show: MyClass myClassMethod.
Using Java as example, I see no difference from
class MyClass {
public static string myClassMethod () {
return "Hello from class method";
}
}
System.out.println(MyClass.myClassMethod());
> Btw, if you're wondering from where the convention of using static methods for operators comes from, that's another relic of C++, a language not known for elegance, finesse or for being a good OOP language.
Better know C++, before bashing it.
In C++ operators can be defined as member functions and participate in inheritance resolution except for operator=.
I'm a JS developer who tried to adopt Reason for a full stack side project in September. There is so much to love about the language and ecosystem. After about a week I decided to start over in TypeScript for a single reason: the awkward interop with promises compared to async/await, which is now available in evergreen browsers and Node 8 without compiling down. I'll definitely try again once this is ready.
Haskell does not allow accidentally mixing floats and integers together. In fact it doesn't even allow mixing finite and infinite-precision integers together, despite using the same operator for all number additions:
Prelude> (3::Int) + (4::Integer)
<interactive>:2:13:
Couldn't match expected type ‘Int’ with actual type ‘Integer’
In the second argument of ‘(+)’, namely ‘(4 :: Integer)’
In the expression: (3 :: Int) + (4 :: Integer)
In an equation for ‘it’: it = (3 :: Int) + (4 :: Integer)
It's not by any means the only language with that property either.
Off topic: why on earth does ghci print out that last “In an equation for ‘it’: it = […]” statement? Seems like something internal to ghci, and not something that’s relevant to the user.
You'd have to check ghci's source itself, but I expect it just hands the bits over to GHC for evaluation and the relevant GHC API (sensibly) requires a name, for which ghci provides "it".
There's some pretty fundamental reasons that ocaml does not support open classes/modular implicits in that fashion.
Essentially you want Haskell's numeric tower, and while it's pretty amazing it's worth noting that it requires a lot of machinery with big implications.
> Essentially you want Haskell's numeric tower, and while it's pretty amazing it's worth noting that it requires a lot of machinery with big implications.
> The second is a good concurrency story, ideally one that is compatible with JS-style async/await keywords for easy interop
Algebraic effects are also in the works which will help with stuff like async/await, but works over arbitrary effects (not just async). Like monads but nicely composable without the ickiness of MTL. Alas it's still unclear what the ETA is. :( Something I'm really looking forward to!
Do you write Scala, or have you even looked at Scala code any time in the past few years? I'm curious where this habit of spreading FUD over Scala being operator heavy came from. Maybe it was back when it first started reaching the HN front-page or something?
Scala is a super approachable language, without any (that I can think of) strange operators in the stdlib. You could make a case for Cats, I guess, but writing purely functional Scala isn't necessary at all, and is something you can approach when or if you feel comfortable to do so.
I'm not the biggest fan of Scala, but for me it's more that it reminds me of Java than the functional constructs.
But pointing at Scalaz is a bit ridiculous. It's a known test library for trying things out and basically not allowed in any Scala codebase I've been working on.
Scalaz is widely used and widely panned. Even though I'm a haskell and purescript programmer that prefers to write <$> and >>=, I find Scala's approach frustrating and hard to read. Especially as type lambdas stack up.
I'm just saying; the impression exists for a reason and the reason is a popular and well-known library.
I think the case is obvious for integer vs floats -- addition should be addition.
Many languages don't use "+" for string addition, because string addition is not commutative (a+b != b+a)
SQL, for example, uses "||" for this, and Haskell uses "++"
That being said, argument type overloading is important for clarity. One of the famous failures in Haskell is "map" only works on lists, and "fmap" on other functors.
I'm hoping the movement around ReasonML[0] could lead to both the web community being able to reap the benefits of OCaml's fundamentals, while also giving people a point of introduction to the OCaml community through web dev.
It has been my experience that the ReasonML community is incredibly welcoming to new comers, and is moving at a break-neck pace to find the right target of being approachable for people who are familiar with JS, but also empowering users to use all of OCaml's power to create web apps that are more simple and correct than they would otherwise be.
The short answer is that Reason's syntax is simpler and more enjoyable to use. I simultaneously learned both OCaml and ReasonML syntaxes and find Reason to be much easier to work with.
OCaml's syntax on the other hand is stable, whereas it is my impression that ReasonML changes with the same frequency that JS libraries are changed.
And the differences are very superficial. What trips people is not the actual syntax, but the concepts learned, the rules of the type system, scoping rules, etc, which don't change.
I'm glad that ReasonML exists, but personally I would use OCaml instead of risking that my codebase gets stuck with an old release after only six months because the current version now has different opinions about how to express anonymous functions.
You're right in that ReasonML syntax is a bit of a moving target, but I think it's part of the initial pains of their effort to provide an approachable port into the ML world. The syntax should stabilize eventually. The maintainers are aware of the churn they are creating, and have deemed it worth it.
That being said, I have a bunch of old projects written in Reason 2 (which still work; the tooling has preserved perfect backwards compatibility[0]), and recently started a new project using the new Reason 3 syntax. It was easy enough to pick up the new syntax in about a day. I haven't done it yet, but my understanding is that it's completely automated[1].
I learnt Reason syntax first, then OCaml second. I prefer the OCaml syntax, it seems simpler, cleaner, and doesn’t hide the nice things about the language behind JavaScript semantics.
The biggest example I can think of is that OCaml uses let..in, which reminds you that every function is a statement. By contrast, the `;` in Reason hides that fact.
That said, it was the Reason syntax that first brought me to the ecosystem, and I think it’s going to be the catalyst that makes OCaml a big player in front end.
> “The biggest example I can think of is that OCaml uses let..in, which reminds you that every function is a statement. By contrast, the `;` in Reason hides that fact.”
... Except in OCaml modules/files where you don’t use “in”. OCaml still has “semicolons” for let bindings - but they are just spelled as “in” or “let () =“ depending on the the context. All but one of these “semicolon” forms have nothing to do with language semantics and are only used to help the parser figure out how to group bindings and values. It doesn’t make OCaml’s core semantics worse or better- though it might make it harder to learn initially or copy/paste code between different contexts.
The point of ; in Reason is merely for consistency - to have exactly one way to form bindings whether you are in a module or expression, allowing you to copy paste lines between the two contexts. Compare that to OCaml which requires different syntax depending on the context and copying values from module bodies to expressions requires a lot of editing.
There may be other ways to achieve the same kind of consistency without semicolons and we are open to them - just so long as they are consistent across all contexts and easy to learn. (And as long as they don’t have major foot guns like JS’s ASI).
(Often people object to ; as a parsing separator because they think it means “side effect”. It doesn’t. Not in Reason. Not in OCaml. OCaml features ubiquitous use of semicolons for everything from arrays, lists, to records field separators, all things that have nothing to do with side effects. And if you still haven’t had your fill, there’s the ;; double semicolon - which actually does typically imply side effects).
I do miss a lot of the parentheses-less-ness of OCaml (and the old Reason syntax), but I'm honestly a lot more comfortable at least broaching the topic of using it on some work projects now that it's more JavaScript-y.
Probably because they were first trying to sell an ML internally and it sounded like OCaml/Bucklescript didn't catch on. I think the new syntax is the raison d'etre to some extent.
Yeah, for better or worse that's ort of the default. I think ML syntax is fine, and in some cases great. However very few CS programs seem to teach MLs and AFAICT no bootcamps do so these languages tend to get picked up by dedicated autodidacts and academics. ReasonML might be a nice bridge,
What's wrong with the libraries and community? There are thousands of libs in opam repository and although community may be smaller than Go's, the quality of OCaml community is incredibly high.
I wish SML were more popular. The syntax is much better and regular. I'll be done teaching you advanced SML language features before I could finish teaching the basics of ocaml.
SML also has a multithreaded implementation in polyml and a better compiler in mlton. The standard library is not big enough, but at least there aren't 4 like in ocaml (std, core, batteries, and container).
The only issue is that just a little more syntax would be nice, but the next gen group is working on that.
Nowadays, OCaml has so many more syntactic sugar and typing goodies compared to SML, that it's not fair to compare them anymore. I'm no expert in SML, but from what I know:
- OCaml's pattern matching is more powerful (more or-patterns, lazy, matching records with field-puning):
`match x with lazy {x; y; z=(None | Some 0); _} -> x+y`
- GADTs along with phantom types: many things are now type-safe (including the builtin format strings)
- inline records in sum types: `type 'a tree = Empty | Node of { left: 'a tree; mutable key: 'a; right: 'a tree; }`
also, I prefer `let x = y in …` rather than SML's equivalent which forces nesting because of the final `end`.
In addition, flambda generates very good code. I don't know if it's as good as MLTon, but it's definitely an improvement.
Ocaml is a mess. Just like C++, PHP, or Perl, it's spent way too much time bolting extra stuff here and there until different codebases can look like completely different languages. Ocaml's features don't flow well together and make it very hard to teach the language (as used in the real world) to new developers.
Golang has fewer features than Ocaml and is worse in almost every way, but people love it's simplicity. The best language features are the ones you can add without significantly increasing syntactic complexity.
I agree that Ocaml has some great features that the current SML spec lacks. That spec is 20 years old now. There's plenty of room to look at Ocaml features and pick a good set that does enough of the things without blowing up the complexity.
SML's let expression is definitely superior to Ocaml's. Ocaml has two different `let` expressions -- one for top-level and one for sub-expressions.
(* Ocaml *)
let six = 6
let rec fact x = if x = 0 then 1 else x * fact (x - 1)
let six_fact =
let six = 6 in
let rec fact x = if x = 0 then 1 else x * fact (x - 1) in
fact 6
(* SML *)
val six = 6
fun fact x = if x = 0 then 1 else x * fact (x - 1)
val six_fact =
let
val six = 6
fun fact x = if x = 0 then 1 else x * fact (x - 1)
in
fact 6
end
SML groups multiple declarations together which provides greater clarity IMO while having an end keyword helps identify the let block.
I wish OCaml could just use the libraries of Go and Rust. They can both compile to object files, and their type systems can be mostly modelled in OCaml's.
OCaml can call external libraries via FFi functionality. I've only done this with Bucklescript (which makes it easy, the JS doesn't need to be compiled) though RWO walks through binding a C library: https://realworldocaml.org/v1/en/html/foreign-function-inter.... Wonder if this could be used with the go/rust compiler?
It's definitely possible today, I just wish it were closer to seamless. For example, it seems like it would be possible to write a utility that takes a Go source file and emits a .cma, and maybe an .mli so you can see what's in there.
My biggest gripes are, in order: lack of library documentation, discoverability, maturity, and existence. Disclaimer: this is all anecdotal and the last time I dove into OCaml for side projects was about a year ago.
1. Documentation. Anecdotally, when you find a library that's not by Jane Street or top-20 starred on GitHub, the odds are low of finding a useful README or example code. God bless the developer if there are tests, but that seems to be rare as well.
2. Discoverability. OCaml's ecosystem sees tons of code for useful but non-major tasks floating around in various GitHub repos, where installation is tricky. I want to use a part of speech tagger? Great, I found a library with no documentation on GitHub - now how do I get it into my application? Why isn't it in the OPAM repo? I found an elasticsearch client library with no documentation on GitHub? Great - now why isn't it in the OPAM repo.
3. Maturity. Of the myriad library code floating around on GitHub, very little of it is what I would consider production-ready, by merit of lack of testing, lack of community, lack of documentation, and lack of frequency of updates.
4. Existence. Frequently, libraries don't exist which do in other languages. In the past couple weeks, I've pulled in Go and Java libraries for crontab parsing and execution, a MySQL ORM, inflection of English words, and various NLP utilities like the Stanford parser.
All languages have these problems to some degree, but the fact of the matter is that while OCaml is a beautiful language for closed domains, I can't in good faith recommend it for building production systems to interact with the outside world. Other languages give you many of the benefits of the type system with much less of the hassle (looking at you, Kotlin). Writing production systems in OCaml can be done, absolutely - my hat is off to Yaron Minksy and the folks at Jane Street - but there are tradeoffs.
And we're not even getting into developer tooling; this is just libraries.
All counterarguments welcome; it'd be wonderful to hear that OCaml is gaining in developer productivity and production readiness over time. I have great faith that ReasonML can get lots of Javascript folks more interested, and the increased demand for libraries should hopefully lead to the filling of holes in the ecosystem. Most of OCaml's problems stem from it being simply unpopular.
This is some solid feedback, thanks for taking the time to write it up. I work on ReasonML, so a lot of this feedback describes problems we’d like to help the OCaml community solve. We are making progress on the popularity front by leveraging the JS ecosystem and it is proving to be an effective approach due to the size and openness of the JS community. Meanwhile the OCaml ecosystem (libraries, compilers, build systems(see jbuilder and its docs)) have been improving in the last two years so I would recommend checking it out sometime soon again.
I had a different experience with OCaml libraries. I’ve found some great gems in the OCaml ecosystem. For example, Menhir is an excellent parser generator used in Reason itself and it’s well documented and very powerful. I think many of these great pieces of the ecosystem stay hidden due to the exposure problem you mentioned, which comes back to popularity.
Another major missing piece of the library story was a way to build web apps using the most common web frameworks so we developed Reason React as part of the ReasonML project. https://reasonml.github.io/reason-react/
It’s definitely not a “bolted on” approach to React - it’s carefully thought out and in my opinion the API improves significantly upon React in ways that can only be done with language level type system features. Reason React is how I wish React always was.
Apologies for hijacking the GraphQL library thread to plug our project.
My pleasure, Jordan - thanks for the great work you and your team do on ReasonML.
I agree that OCaml has some brilliant libraries for problems which are, in other languages, difficult or unpleasant to solve (parsing being one of them). Menhir is wonderful. However, that doesn't preclude gripes 3 & 4 from above. If a language seeks to be general-purpose, the existence of some truly great libraries doesn't redeem a need for dependable solutions to a multitude of problems.
I think OCaml can have a place in an organization's software stack today - but it will not gain the spotlight until its library offerings have the variety, robustness, and accessibility of other ecosystems. Would I write an internal parsing service using Menhir and expose over Protobuf? Absolutely. Would I write a heavier-duty application server that had to parse something, then talk to Postgres? Perhaps not - and that's a shame.
Love Reason React. I'm a huge static typing proponent where possible, and combining the semantics of OCaml with React is a great move.
Writing the occasional typed FFI wrapper into a Javascript library isn't the worst thing in the world. Most of my complaints for libraries center around server side OCaml/ReasonML development, which is where I have the most experience. (and that experience is admittedly limited)
Sure, ocaml does not have javascript/go-style libs like leftpad, but it provide a great ability to solve generic tasks with more generic libraries like angstrom, menhir, faraday, lwt or react.
>I can't in good faith recommend it for building production systems to interact with the outside world
Why is that so? I believe in exactly opposite. We are building quite a big telecom infra with ocaml, and it shines. In really critical systems (something beyond silly guest pages) you can't rely on low quality third party libs (which are usually provided by go), you have to write things by yourself or use some very generic stuff, and that is where ocaml shines. Both the language and major libs in opam are of an outstanding quality, people behind mirage, ocaml, angstrom, react, lwt are so collaborative and smart, and libraries are so transparent and beautiful that it is easy to understand how things work and what's happening under the hood.
Totally fair - no experience here in deploying OCaml for production. Agreed that the language and major libraries are phenomenal. My intuition is that the maintenance time cost of writing and extending these abstractions from scratch in the face of changing business requirements is higher than the time cost of using a less perfect language but richer ecosystem; but that could be totally wrong.
Glad the language is shining in your situation. Can you elaborate more on team composition, tenure, learning curve, and use case?
> 10 minutes with angstrom and unixlib?
https://github.com/robfig/cron is ~1.5KLOC - sure, it's Go, but that's a bit more than 10 minutes in any language, especially to change gears and think through edge cases. I'd prefer for things to Just Work.
> low quality third party libs (which are usually provided by go)
My experience with Go libs is that because the ecosystem is large and expanding fast, a surprising number of problem domains have one or two great standout libraries among a ton of noise. But it turns out one or two great libraries is frequently all you need. I dislike the language from a theoretical standpoint but after working full time in it for several years, can't deny that it's good for Getting Shit Done.
>requirements is higher than the time cost of using a less perfect language but richer ecosystem; but that could be totally wrong.
Believe me, breakdowns will cost much more, especially if your device has no ssh, connected via ASI/some military obscure one-directional interface. I found that in many fields it's incredibly hard to approve third party libs usage, even some basic stuff like boost.
>~1.5KLOC - sure, it's Go, but that's a bit more than 10 minutes
cron should be easier, it's either vars or cronlines, which are trivial. I prefer to write it in angstrom than to catch some unexpected panic in go because of type coercion or similar shit.
Having spent my entire career, 15+ years, in "weakly typed" or whatever you call it, languages, such as basic, vbScript and JavaScript I don't get this type hype. In "unsafe" languages that have overflows I get it will help to make it less unsafe, but in high level languages such as JavaScript why do you even need static (not sure I'm using the right vocabulary) typing !? Such as HypeScript, err I mean TypeScipt. Is there an entire field of programmers out there that get bugs because they are mixing numbers and strings !? I also did that a lot as a beginner in JavaScript as the plus sign is used both as concatenation and addition.
I read a study here on HN a while ago (that probably was sponsored by $M) that stated using TypeScript would prevent most of the bugs found on GitHub, but of course there where no examples of where type annotation would have helped. So what's with this type frenzy ?
I write this letter from the distant past, late in the month of November in the distant pass of 2017. From your lofty throne upon a future so bright, I urge you to remember the sad lives we lead as a return code type mismatch caused every Mac OS X machine running modern software to be accessible by anyone with physical access by typing "root" into the login field then hammering on the Enter key like a 9 year old.
Morale is high, because we know we cannot be sued for this act of gross incompetence. We have decided to solve this problem by shaking our heads at people who code in C and ignoring any similarities to our own toolkits.
> a return code type mismatch caused every Mac OS X machine [...]
I don't doubt you, but I was just trying to read up on details of this and the internet is so full of fluff pieces that I can't find anything technical real quick. Would you have a link to a writeup of the bug behind this issue?
The relevant function returns an opaque integer to signal failure/success. If this were typed properly — e.g. a type that represents either “Success” or “Failure” explicitly (rather than implicitly via an int) — the bug would be unlikely to happen, since it would require the function in question to explicitly return “Success” when in fact it had failed (as opposed to the opaque 0x01).
In this specific case, strongly typing the CryptVerificationResult as an enum would have helped. But, generalizing the problem, if you had more complicated criteria for whether that branch should be taken, it can be far too easy to save an intermediate result into a (strongly typed) variable but never end up using it in the correct way in the condition statement itself.
Rust goes part of the way here by ensuring that Result variables get checked: https://doc.rust-lang.org/std/result/#results-must-be-used which to my knowledge is not something you can mimic in C/C++. But you could still forget a negation or use && instead of || somewhere, and have an uncommon code path fail.
Code review reduces but doesn't eliminate the probability of these complex logic errors. What's really needed is tooling that ensures test coverage on a phrase-by-phrase, not line-by-line, basis. (Basic line coverage would have said that all these lines of code were executed in testing.) And you need a culture around paying attention to those results. That can be very difficult to build, but for mission-critical software (security included) you absolutely need that level of attention to detail.
> In this specific case, strongly typing the CryptVerificationResult as an enum would have helped. But, generalizing the problem, if you had more complicated criteria for whether that branch should be taken, it can be far too easy to save an intermediate result into a (strongly typed) variable but never end up using it in the correct way in the condition statement itself.
We can capture that requirement with something called Linear typing, but even if we don't go for a compiler-enforced consumption the creation of a universally used family of result enums brings enormous discipline and reliability to error handling. In no small part because when considering the result of such an enum, the compiler can demand a total pattern match, which forces developers to consider what that failure means in context and present SOME kind of strategy (even if it's hard failure).
The approaches of languages with type inference and more sophisticated type systems like OCaml and Haskell go even a step further because they can create workflows around these types, creating composite workflows that make it easy to handle errors. It becomes harder to ignore them, or write functions that ignore them.
One of the reasons it's so easy to write parsers in languages like Haskell is that algebraic data types and applicative functor composition make it easy and even convenient to talk about the logic within the context of non-trivial error flows. It's much more frustrating to write a parser without a combinator framework in OCaml or Haskell. Similar stories exist for Validator, Either, and Maybe/Option.
These techniques don't mandate error processing, but they make it more convenient to compose error-handling functions and offer more compiler checking when the results must be handled.
While disassembled code is hard to read I think it's not a problem with weak types. The problem seems to be the root account got enabled without a password.
It is crystal-clear a type error, but here's an article breaking it down:
https://objective-see.com/blog/blog_0x24.html notes that od_verify_crypt_password is called with an error check expecting 0, but on failure it returns 0x1. The code then goes on to a "success" path with an "update".
This is a classical type mismatch that C and C++ pre-2012 are so famous for. Someone writing this program where both modules were in Rust would have used Option or Result and this expectation mismatch wouldn't have existed.
It is still not clear, it seems more like a logic error to me. Zero means success and a non zero value is an error code. It basically says "if login fails, then do this". If they would have used a boolean instead, or a result enum, the code would have taken the same path! I argue that strong typing would not have detected the logic error! The bad code was probably rushed in to make the upgrade work. All tests turning up green can really put you into a false sense of security. But that is another strongly opinioned topic!
It's 2017 dude. C is weakly typed with a wholly insufficient type system. We're talking about statically checked type inference systems now.
A better type checker, better standards and practices, and better discipline around customer facing code are all required, but folks like you demand that every programmer must do it all by hand and then never make a mistake. It's ludicrous.
This is like you saying, "Well cars are so unsafe you can't expect to live if you ride in one. Look at these pictures of horrible car wrecks. These Model Ts are death traps. But we all drove to the talk in Teslas and we're not even sure what the heck you're on about.
Don't use your lack of (or refusal to obtain) familiarity with type systems and type theory as evidence of their inability to help with these problems.
If you don't make type mistakes, good for you, but why not let the compiler or interpreter provide a safety net, just in case? In addition, static typing makes refactoring easier because you can confidently redesign your code and let the implementation tell you which types you haven't yet adjusted. Besides, type inference means that you do not even have to write out the type yourself in many cases. (However, to do so is better for documentation.)
Also, why do you think that Microsoft is secretly pushing static typing? /Maybe/ it's shilling for TypeScript, but there are a lot of other statically typed languages that compile to JavaScript, such as Elm.
I have nothing against type inference, static analysis, or type notation for the sake of performance optimizations. For a high level language I think it's up to the compiler to figure out the right type to use and keep things safe. And I think static typing is over-hyped as the holy grail of programming. And you can still use type annotation for documentation in a dynamic language by naming the variables such as nFoo = 1, strFoo = "foo", arrFoo = [], although I advocate just giving the variables prober names such as age = 1, name = "foo", friends = []
JavaScript is the perfect enterprise language. And that's where $M makes most of it's money. But they do not fully control JavaScript, and they have little control over NodeJS. So what do you do ? Embrace, extend, and extinguish! You add enterprise feature! That will convert users back into your ecosystem again! While at the same time trying to hurt JavaScript and NodeJS. Interestingly though Samsung recently bought NodeJS and their new devices does have JavaScript support, so hopefully NodeJS and JavaScript will not be too easy to extinguish.
How many multi-year running, 20,000+ LoC applications have you kept up to date? Or do you just start from scratch every year because it's impossible to upgrade your dependencies?
I've done my share of legacy code maintenance in $M vbScript. My main issue is not lack of static typing, but something I call importception, where you have a lot of include/imports and imports within imports with lots of global variables that change here and there. With NodeJS though all variables are local and imports (require) is scoped, which has been a huge relief.
The typeyness of graphql gets you documentation, validation and a way to communicate intent with fellow developers in a way that's agnostic of the language and transport used. Win, win, win, win.
In case anyone on the OCaml team is reading this though, there are two language-level changes that I think could do wonders for wider adoption. The first is modular implicits: it feels so kludgy to have to type `a + b` for integers, `a +. b` for floats and `a ^ b` for string concatenation. I know it sounds like a small thing, but it makes the language feel inelegant, and aesthetics are important. The second is a good concurrency story, ideally one that is compatible with JS-style async/await keywords for easy interop.
I know those are both being worked on; I just hope they become available in time to coincide with the wave of interest in ReasonML!