Leaving OCaml
blog.darklang.com
blog.darklang.com
> you mostly model data with sum types, which in my mind are the best way to model data
>it's very similar to the language I wanted to build (in particular, we could reuse built-in immutable data structures for Dark's values)
> it had a reputation for being high-performance, which meant that we could write an interpreter for Dark and not have it be terribly slow (vs writing an interpreter in python, which might be too slow)
> We have struggled to make editor tooling work for us.
> Lack of libraries
> Multicore is coming Any Day Now™, and while this wasn't a huge deal for us, it was annoying.
> we also use ReasonML
> lazyness and pure functional-ness I felt were not great
> I plan to leave keep the frontend in ReasonML
You want a statically typed functional (but not pure) language with sum types, immutable data structures, good performance, editor/IDE support, a broad library ecosystem, multithreading, a C-like syntax (I assume, since you're using ReasonML), and a nice compile-to-Javascript experience to run in the browser.
I don't want to sound too much like a fanboy, but all this really sounds like Scala. You want Scala.
:)
You the author or something? I'm actually learning F# and OCaml and am interested in Scala as well.
[1] just looked at it again and while it doesn't default to mutability, it doesn't default to immutability either (i.e. to compare it with F#, it's 'val vs var' versus 'let vs let mutable', so the latter being much longer means the default/lazy thing to do by devs is immutability, in the F# case).
val immutableValue = mutable.Map.empty
immutableValue.insert(key, value)
In this case the distinction between "val" and "var" is at best a misdirection. The only language that I know of that explicits interior mutability is rust. Where your map must have a "type marker" `&mut` to be able to use the `insert` method. The only way to have that type marker is to define the map with a `let mut`: let clearlyImmutable: Map<String, Int> = Map::new()
clearlyImmutable.insert("hello", 10) // Does not compile
while let mut clearlyMutable: Map<String, Int> = Map::new()
clearlyMutable.insert("hello", 10) // Compiles :)
The conclusion here is that if you are not working with rust, immutable data structures (where no method mutates the object it refers to) are what guarantees you you won't run into spooky mutability at a distance.However, it would be disingenuous to not mention that you can run very quickly in the wild into scala code that is just "java without semicolons". You can very deliberately break null safety in scala. For example `val x: (Int, Double) = null` does compile. You won't normally run into `null` unless you are interacting with java libs or with code from programmers who don't understand type safety.
I want to point out: scala let you do all those nasty things, but this will be a deliberate choice. The mutable data structure from the standard library are clearly marked. There is no methods in the standard library that returns null (outside of regex methods which are thin wrappers around java regexes). Writing mutable code requires a completely different architecture and design choices. If you are in an immutable team, it will be very hard to justify that kind of code. While in a mutable team, the reverse might be true.
In Rust the 'interor mutability' term is used for types like `Mutex`/`RefCell`, `Cell`, or atomics, which are mutable even through shared references. These come with the same problems as interor mutability in scala.
Compare https://rescript-lang.org/docs/manual/latest/shared-data-typ... with how Scala.js represents arrays and records.
Regarding interop and the link you posted, here is the equivalent documentation page in Scala.js: https://www.scala-js.org/doc/interoperability/types.html
Yes, Rescript has more built-in types that map straightforwardly, but that is at the cost of some correctness. For example, using a JavaScript array means that JavaScript can resize it under your feet. Using `undefined` to represent `None` means that you cannot tell the difference between `None` and `Some(None)`. It's a fine trade-off to make. Scala.js happens to make the other trade-off, and offers separate types for JavaScript interop.
I like the concepts and design of Rescript, really. It's very interesting because they have all the essential requirements right (IMO) like comprehensive JavaScript interop, while making all the opposite design decisions on nonessential trade-offs compared to Scala.js.
ReScript is more correct than scala, ReScript's type system(essentially borrowed from OCaml) is sound while scala is unsound.
> Using `undefined` to represent `None` means that you cannot tell the difference between `None` and `Some(None)`
I don't know where you get the impression, of course they are different in ReScript.
One thing missed is that ReScript compiler may be 10-100 times faster than Scala.js (I am not kidding)
Ah, I did not make it clear what I meant by "correct" in this context. I meant "faithful to the original language" (OCaml natively compiled and Scala on the JVM).
You are of course right about the type systems. (I believe many soundness holes are fixed in Scala 3.)
> > Using `undefined` to represent `None` means that you cannot tell the difference between `None` and `Some(None)` > > I don't know where you get the impression, of course they are different in ReScript.
I get that impression from the document linked above, which says that `None` is represented as `undefined` and `Some(x)` is represented as `x`, so `Some(None)` must be represented as `undefined` as well. Oh and `()` too. Do at run-time you can't tell which is which.
> One thing missed is that ReScript compiler may be 100 times faster than Scala.js (I am not kidding)
Yes, I believe that's true. It is often mentioned as the biggest weakness is Scala, by advocates and detractors alike.
Most of the other cases, you can use typed_arena or bumpalo.
The other 0.1% of the time, you'd have to be pedantic about allocation anyway and might appreciate the compiler's help.
I think you mean borrow checker for ownership issues...
> why doesn't Haskell take the same route as Rust and ditch its garbage collector?
The Rust language was designed in a way (with ownership) that doesn't need a GC, while Haskell have many different properties (such as lazy evaluate everything) that I don't think would work well without a GC.
The borrow checker is usually a nuisance when you're writing highly mutable code, which isn't a problem for functional code. If you need to store the same data in multiple structures, the sibling is right, .clone() and .to_owned() will usually fix your problem with a very small overhead.
There are use cases where OCaml’s type system leads to elegant type-safe code that, if expressed in F#, involve inelegant unsafe boilerplate. It’s usually not a whole lot and it’s typically routine but it certainly happens. For some teams and products OCaml is a better choice.
But there’s also a reason why many developers abandoned Lisp for Python: some theoretical fanciness and robustness only infrequently outweighs ease-of-use and quality-of-life.
I understand his point here. Spending week(s) writing a good SDK is hard to justify, but if you're building new infrastructure to support a project in a more obscure language, you kinda have to sacrifice time building said infrastructure well. When Naughty Dog programmed their games in GOOL/GOAL they built their own compilers, debuggers etc. - not a trivial task.
I don't think that this bias is a bad thing. It's nice to read about cool technologies that you might not use in your day job. But anyone using HN to decide on the right tool for a project should carefully consider other sources of information as well.
But if you say that Common Lisp is great, there are fewer people who have really used it, so fewer people who have used it and didn't like it. And since fewer people got into it unless they really wanted to, it's easier to say "You didn't really understand it, then, or you would have liked it." The number of people who can say "No, I really got it, and I still didn't like it" is relatively small.
So far, that's just demographics. But then you get into the language itself. Java isn't an exciting language. It's not fun. It's kind of corporate and bland. There aren't many people who are really enthusiastic about Java - nor does Java give them reason to be.
GraalVM is doing some amazing things with polyglot support and native compilation.
Java's project Loom is trying to bring fibers in a really nice way to the language.
It is slowly working on bringing value types and inline types as well.
And it's got a few cool GCs being experimented on, as well as a nice foreign memory API.
Java recently got really nice improvements, multi-line strings, text blocks, local type inference, a shell, running code from source scripts and switch expressions.
Moving from OCaml to C++ or Go would be a drastic and surprising change, but it's not nearly like that.
There's some really great stuff about OCaml. I don't think we could have gotten through the first 2 years without OCaml, we repeatedly made very large scale changes to what our product was and how it worked, and that would have been 10x harder in something else.
I actually think OCaml stands to be in a great place in the next few years with the work being done by core community members. See https://twitter.com/patricoferris/status/1323330884515356672.
Also why not to choose F#/Mono (and now .NET core) for example?
I don't remember considering F#, so probably just never occurred to me.
At the same time, it's a good response when somebody muses in a saddened manner "I wish my favourite language was more popular". Then I can spring out of the bush and tell them why it isn't. This doesn't mean I expect the OSS community around the language to serve me -- of course not! But it's a good explanation on why language X isn't more commercially popular.
I don't really agree on the "Learnability" and "Minor annoyances" sections here. In terms of overly academic discourse, well, everyone's experiences differ but that's simply not a problem I've personally seen in the community (and I'd happily tell you other languages have that problem in spades). In terms of learnability: Ocaml, (the good parts) is an extremely straightforward language in most respects. The tutorials on the Ocaml website are enough to get you up and running, and Real World Ocaml is freely available and it's great. It'd be nice if v2 ever came out, but...well. Between those two, if you're still struggling, give me a call and I'd be happy to tutor you :)
The rest of this article hits most of the nails right on the head. The library support just isn't there, the community is too fragmented (why is stdlib vs. Core vs. Batteries and Async vs. Lwt still an issue?), the tooling is still too rough. OCaml's lack of multicore isn't actually a problem for most use-cases, but the fact that it's been in limbo for a decade despite the endless clamouring for it tells you something about what you're hitching your wagon to.
Twenty years ago OCaml would've been a no-brainer. At this point if I wanted a practical garbage-collected language I'd choose Scala and get a large and active community, as many Java libraries as there are stars in the sky, and the most sophisticated runtime environment mankind has ever dreamed of (for better or worse). If I truly need a compiled language, well...tragically, I've been bitten by the Rust bug (to the extent that I'm starting to believe its rigid approach to ownership helps engineer better code - that the ability to freely and thoughtlessly sharing references to objects the way garbage collection grants makes it a mistake, but that's a story for another post). And if I need 100% of the power and safety types can give me, Haskell is there waiting, with a better ecosystem to boot. Everything OCaml does well other languages do better, and then some. It's an also-ran now.
Hahahaha oh wow. You never ever used Scala, aren't you? I did, I moved from OCaml to Scala.
Oh, good luck joining Scala community then, we don't have aforementioned problems, don't we? Because Akka vs Zio vs Cats vs Scalaz is not a thing. Because ensime vs metals debacle never happened. Because we don't rewrite the complete ecosystem each couple of years.
> most sophisticated runtime environment mankind has ever dreamed of
Which you need to tune quite a lot to fit Scala into it (hopefully, it's very tuneable). Oh, and this "most sophisticated runtime" doesn't support TCO btw.
Oh, and any AnyRef value (so anything but basic types) can be null, thanks, runtime.
I love Scala, but all these "I wouldn't choose OCaml, I would choose F#/Clojure/Scala/other obscure language" usually mean "I've tried ocaml but didn't try other obscure language, so maybe it's better"
I love OCaml is well, and most of your points could be reduced to simply "it's lacking manpower" (Scala is lacking it too).
Because all of the "why is stdlib vs. Core vs. Batteries and Async vs. Lwt still an issue" we have in C, C++ and many other popular languages too, but it's not a big issue. People complain on meson/cmake/autotools/boost/glib/qt but not leave for some reason.
Maybe people don't use OCaml that much rather because there are simply too few jobs and too few programmers and it's a simple chicken and egg problem. And all these technical rationalization is not the reason because it's applicable to quite a lot of languages (safe for lack of libraries)
> I love OCaml is well, and most of your points could be reduced to simply "it's lacking manpower" (Scala is lacking it too).
For the better or the worse, this is literally the #1 priority one has to have before trying to technically evaluate a language / framework these days. Otherwise you get sucked on a joyful ride at the end of which you find lack of employability. :(
I personally much prefer OCaml's syntax but since Rust has much more mindshare and is mostly serving the same niche (with GC being the apples-to-oranges comparison here), I preferred to work with Rust. I still would like to work with OCaml but the odds are stacked against the financial incentives of doing so.
> Because all of the "why is stdlib vs. Core vs. Batteries and Async vs. Lwt still an issue" we have in C, C++ and many other popular languages too, but it's not a big issue. People complain on meson/cmake/autotools/boost/glib/qt but not leave for some reason.
Sure, many other languages suffer from that but it's still a very good ideal to strive for -- namely have very few (ideally one) ways of doing things. I personally look for such languages / frameworks and sadly they are very few and far between (Elixir and Clojure come to mind as refreshing exceptions and even they don't follow it everywhere).
---
In the end, we all do this for money. If I could work without charging for it then I'd likely know more languages and even work more overall, but it's not the reality we are living in currently.
Well, people use OCaml, I've introduced and used it in a couple of workplaces. OCaml shines in domains like system programming, and it's worth trying to introduce it for your team and see how it will end (for small projects if OCaml fits the domain).
I did that couple of times and people were glad not only because it's a delightful language to work with, but also because you can learn some stuff while working with it.
> Sure, many other languages suffer from that but it's still a very good ideal to strive for
I think that it's an unachievable ideal. There are two approach: batteries included and community driven.
Former leads to bloated slow half-baked or even straight incorrect implementations of every algorithm or parser or server people need in the standard library (python way).
Latter leads to fragmentation. Neither is perfect, and I haven't seen a language without such troubles.
You know, there might be a somewhat nice middle ground: after several community driven libraries have materialized, maybe include some interfaces in the standard library, that allow users to choose which community library the want without having to hardcode too much to that library.
It's not perfect and wouldn't work for every situation, but might be a way to soften the downsides of the community-driven approach.
But it's still not perfect since sometimes third party libraries still need to write concrete implementation of something diving into lower level, hence breaking the abstraction.
This "interface" approach wouldn't work in all cases.
That's what Elixir did with calendars and timezones. Worked great.
The big reason to not use F# vs OCaml tends to come down to the lack of functors... but it looks like your current codebase might not be using them much.
What do you mean by that? F# has support for functors, or are you referring to something else? https://fsprojects.github.io/FSharpPlus/applicative-functors...
The best I could describe the general concept is that a functor allows you to take something of some type and maps it to another type via some unspecified function.
Simple example: in an OOP language you might have a Map<K, V> class for any type K, V where K must be an instance of an Ordered interface so that the map implementation can do efficient lookup.
In OCaml, you have a Map.Make(Ord : Ordered) functor which explicitly takes a module Ord as its parameter. The Ord module implements the key data type, which must support the Ordered interface. When you (statically) apply the functor, you get back a new module which specializes a map data type to work for the specific key type. E.g.,
module StringMap = Map.Make(String)
The String module implements the Ordered interface, so it can be used as the argument here. Now you can create and manipulate maps of strings to any values.You might be wondering, what's the payoff? It's very similar to the payoff for interfaces--code abstraction. It's just that it's all statically resolved and highly efficient.
It depends on what you mean by "generics". Functors are generics, but very powerful ones, akin to packages in Ada.
They are way more generic than what parametric polymorphism allows you to do in F#, they support Higher Kinded Types, module can have many type variables within it etc etc.
I said 'traditionally' above because OCaml actually has dynamic dispatch now; it has a powerful and feature-complete OOP implementation, and a way to pass around modules at runtime as first-class objects. And in fact people take advantage of these capabilities to build more 'modern' APIs. But I would argue that the functor approach is one of, if not the best, overall.
Instead, they're roughly "functions from modules to modules": https://dev.realworldocaml.org/functors.html. Functors allow you to replicate much of what classes/objects in an OO language offer, but with static instead of dynamic dispatch.
Real experts should let me know if I am mistaken: I don’t think OCaml and Haskell have strictly the same type system, but they are both similarly more expressive than F# because of this specific type -> type polymorphism that in particular lets you do type-checked monads, etc.
In OCaml, functors map between an abstract module and some concrete one. And in Haskell functors are structures that can have a function that maps over their elements. In math a functor is a map between categories. So you can see how all three kinda make sense as functors, but also are different in exactly what they are.
https://caml.inria.fr/pub/docs/manual-ocaml/libref/Hashtbl.M...
I think in OCaml, "functor" basically just means "like a function but for modules". They might technically be categorical functors, but it seems quite different to the meaning in Haskell where they are plainly categorical functors (modulo Hask technically not being a category).
A function S -> T maps a S(ource) type to a T(arget) type, like FunctorOf (-S>) (-T>) does between the source category (-S>) and target category (-T>)
type Functor :: forall (s :: Type) (t :: Type). (s -> t) -> Constraint
class (Category (Src f), Category (Tgt f)) => Functor (f :: s -> t) where
type Src (f :: s -> t) :: Cat s
type Tgt (f :: s -> t) :: Cat t
fmap :: Src f a1 a2 -> Tgt f (f a1) (f a2)
type FunctorOf :: forall (s :: Type) (t :: Type). Cat s -> Cat t -> (s -> t) -> Constraint
type FunctorOf src tgt f = (Functor f, Src f ~ src, Tgt f ~ tgt)
The usual endofunctor type EndofunctorOf :: forall (ob :: Type). Cat ob -> (ob -> ob) -> Constraint
type EndofunctorOf @ob cat f = FuntorOf @ob @ob cat cat f
we have in Haskell can be defined as FunctorOf @Type @Type (->) (->), or type OldFunctor :: (Type -> Type) -> Constraint
type OldFunctor f = EndofunctorOf @Type (->) f type FunctorOf :: forall (s :: Type) (t :: Type). Cat s -> Cat t -> Constraint
class (Functor f, Src f ~ src, Tgt f ~ tgt) => FunctorOf src tgt f
instance (Functor f, Src f ~ src, Tgt f ~ tgt) => FunctorOf src tgt f
The other definitions can be eta reduced type EndofunctorOf cat = FunctorOf cat cat
type OldFunctor = EndofunctorOf (->)(standard caveat that benchmarks suck)
[1] https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
One of the biggest problems they mention is with postgres. F# has a postgres problem as well.
Your options in f# for postgres usage include the following high-level categories:
• Dapper/RepoDb/Other community libs
• Typeprovider based like Fsharp.Data.Npgsql
• EF core (where you build the fsharp design-time support yourself)
• Using C# in another project as a database interaction abstraction
I've had to mix and match for the projects I've worked on because I've had trouble finding a single library where:
1) Joins are supported in a sane way
2) Batch inserts work
3) Interacting with native features (views, functions, SET variables) works
4) It's still in fsharp
It's not bad, but when I work on $DAYJOB projects in Typescript/etc where database access is smooth, I wonder how long it might take to rewrite for the database heavy projects.
Also should mention that I'm very thankful for the libraries that _do_ exist, and the effort put into making them as feature as they are. It's just one of the areas that I don't think is a strength of F# right now.
Definitely a personal take here, but since I've worked for a while with postgres, a "high-level, production web stack" for me is something that has an established, good DX way to work with this popular database.
Managing ADO.NET connections, working around the type systems and writing a lot of mapping/connecting code are all things that don't solve my business problem.
While not prolific, I've written a handful of production web applications in F# (Suave, Giraffe, Saturn, and Razor Pages) as well as other stacks, and have found a hybrid approach that works for my concerns, but I've found a comprehensive solution not as simple as other ecosystems.
I see questions frequently about data access and serialization/validation in F# web apps, which I had specific sections about. Hopefully I'll have some time in the near future to continue the work.
In the mean time feel free to DM me any questions. There's nothing too fancy about the code. If you looked at other production code, and swapped out what your best guess for the F# translation would be, you're probably most of the way there.
Same thing exists from MS SQL https://github.com/Zaid-Ajaj/DustyTables , also great.
Used both in prod. No regrets and wouldn't wan't something else. Also involves zero magic - just raw SQL and a nice functional API.
I believe there is some work with F# analyzers to provide compile-time checking of raw SQL, although I haven't tried it yet.
Yep, never tried it either. https://github.com/Zaid-Ajaj/Npgsql.FSharp.Analyzer
They'll not going to pick F# because they're in competition with Azure.
They don't want ocaml because of marketability. It's technically the right choice and direction but the learning curve for developers is turning them off the platform as a whole.
Yes, there are ways to write it in a .NET-y way in F#, but then it requires a "complete" rewrite and F# gives no real benefits with "being similar".
In contrast, after that I migrated to Rust and I am delighted. I wrote about it here: https://news.ycombinator.com/item?id=24893285
Of course, this is also a full-rewrite without using the same idioms as in OCaml (mostly because I mutate objects directly when needed) but I still think it's much better than F# as the language (Rust) is miles ahead in the practicality domain. Indeed, it's much better than anything I've seen so far. And still so very much "functional" and "elegant". I just miss the transparent currying from OCaml.
That's a bit overstatement. You can call .Net code from F#, just like you can call Java from Clojure or Python/Rust/C from OCaml.
It just would be foreign and unidiomatic (hello, null pointers, methods, OOP).
And you can call C/Python/Rust from OCaml, we made quite a lot of wrappers for Gstreamer and some Rust libs in OCaml and it was quite painless.
So it's a bit of simplification, you can call foreign code in OCaml, it's not that much harder than F# considering there are higher level FFIs for C, Rust and Python available. Author could just use these as well.
This is because F# and C# share:
* build tools
* package management
* garbage collector
* base types (string, int, etc).
The F# type-checker understands C# types, so you know that you are consuming the C# API correctly (at least at the type-level). F# can be be used as a new syntax for C#, if you wanted to go that way.
So that when people are evaluating a PL, they can known immediately what is missing. And people wanting to make a new PL knows what is missing in their ecosystem, the community can jump in to help.
I had the impression that's a general problem with FP.
OCaml/ReScript/Clojure people seem to be a bit more chill.
Been using Crystal for about 5 years.
[0]: https://www.indiehackers.com/podcast/166-sam-eaton-of-crave-...
The savings in just going over to GCP or AWS and filtering your "potential languages" by the list of supported languages seems impossible to overestimate.
If you want something with more safety machinery than Go (and I get it, so do I), I think your real choices for a language that needs to interact with cloud providers are something on .NET, or something on the JVM - you pick one, and you go solve your actual problems instead of having to write your own stdlib.
I'd be way more open to experimenting on the front-end with buckle->rescript or similar, where I'm leveraging existing JS libraries and don't have to worry about high quality database drivers.
Wish them well with whatever the replacement language becomes!
Some people mention Scala as an alternative to OCaml but honestly the language seems a lot less elegant (at least at a distance...) and the community seems to be surrounded in controversy and confusion ([1] ?)
I think a SML inspired language on the JVM platform would be really popular. [2] lists a few but none of them seem to have gained momentum. [3] looked promising but it seems like development has stalled.
1: https://github.com/lampepfl/dotty
While I don't have internal insight to Jane Street, some of their talks and public information seems to support the idea that they are actually doing a lot of similar work to compiler development. They have stated that they tend to do a lot of symbolic logic and analysis, and they make it sound like they create DSLs that traders can more easily use to convey trading strategies.
So OCaml does really well at getting things done, but historically it has been within very narrow definition of the types of things to be done.
We use OCaml as a general purpose tool, and it really excels in that role.
Log analysis compiles logs into an analysis. A GUI repls user interactions.
It all has to do with how you define the abstractions.
It also does seem that OCaml is more popular in Europe than North America but I don’t have any data on that.
"It was pointed out to me in a private communication that the tuple function \x->(x,x) is actually a special case of a diagonalization for biapplicative and some related structures monadicially."
It received 39 pretty enthusiast replies.
I don't expect this sort of discussion on Ocaml mailing lists (please correct me, Ocaml mailing list subscribers!) And I think the reason is that Haskell makes this sort of abstraction very very easy, and makes it pay off, while Ocaml does not. In Ocaml, it's not even useful to abstractly define functors, which are the most basic data of category theory. In Haskell, functors are a typeclass that comes with the prelude and are hard to avoid. And there's no reason why you wouldn't use a biapplicative either:
https://hackage.haskell.org/package/bifunctors-5.5.7/docs/Da...
Scala seemed like a mess at the time (from a distance - no idea whether I'm right).
My attempts at Haskell had honestly not gone all that well, the lazyness and pure functional-ness I felt were not great. Basically, I wanted server-side Elm, and OCaml was pretty close.
Tried it, didn't hate it, figured could always do a rewrite later. And here we are.
I work on and use the .NET Port of Akka from the Scala/JVM world. In those travels it has included reading a pretty decent amount of Scala code.
I wouldn't call it a mess so much as a very -expressive- language. But the cost of that is there are a lot of different ways to do things, and some parts of Scala that are very powerful can be very confusing until you understand them.
My favorite analogy for .NET developers; if they remember their confusion the first time they had to comprehend => delegate syntax, there's lots of little things in the same vein in Scala; ways for the compiler to generate sugar. There's just so -many- of them it can be overwhelming, doubly so when in some cases the wrong choice can in fact have pretty big performance implications.
I would recommend Scala in second, but you really need at least one person who knows Scala well to navigate the wildly divergent styles of Scala programs out there.
Kotlin gives you sum types via sealed classes. In fact it's basically the bare minimum of Java + sum types + some syntactic sugar.
It has rock solid tooling due to Jetbrains support. It has access to the entire JVM ecosystem. It has a huge amount of learning materials out there. And it's got all the resources on how to deal with web CRUD problems you could ever imagine.
It's not server-side Elm by a long shot, but to be fair, Elm would suffer from all the same problems you listed.
If I had that (and, more importantly, trusted every other developer I work with to also have that) I wouldn't value immutability enough to look for it in my language choice.
E.g. you could decide to write extremely mutable code in OCaml, but it's highly discouraged and the happy path is generally to default to immutability.
So you need comparatively little, although not none, discipline to have a pervasively immutable codebase, since there's nothing preventing you at a language level from introducing crazy mutation everywhere.
Haskell goes further, where you have to use functions named "unsafe*" to introduce mutability. Something like Elm goes further still, where, barring bugs in low-level libraries or heavy abuse of the JavaScript runtime (e.g. redefining prototypes of basic types), you essentially have to fork the compiler to introduce mutability (Dark moved away from Elm because, as I understand it, it actually became too restrictive on this front).
On the other hand, in something like Java, you need a lot of discipline to have pervasive immutability and you have to go out of your way to avoid using otherwise common constructs (e.g. if statements since those are statements rather than expressions) that require very kludgey workarounds (encoding an algebraic data type as a fold and using that in place of if statements).
Kotlin is in the middle. If you don't use certain parts of the standard library and don't use things like var, you have pervasive immutability. At a language level this is quite similar to Scala (Scala has a more immutably focused standard library than Kotlin does).
- Kotlin's biggest problem is how it handles nullability (that is, it simply enables you to leave nulls in your program). This plays extremely badly with classical FP, and Reactor specifically, because 1. the Reactor "monad" (Mono) can't wrap null and 2. flatmapping an empty monad is not the same as flatmapping on Some(null) for example.
- Kotlin has no syntactical support for flatmap/map, like Haskell or Scala.
- Kotlin has no pattern matching, inline types are still experimental, and it's type system is limited, it is clearly targeted at the UI/Android folks who are not interested in expressing their data in a static way. Making sure that people don't shoot themselves in the foot is great for the average Joe developer, but not so great for library writers.
Having said all that, it's still better than Java, but it's 2020, ZIO is past 1.0 and Scala 3 is around the corner. I'm really looking forward to 2021...
2. I assume you're talking about `do` notation (bind/return + ap/pure if ApplicativeDo is turned on) and `for` comprehensions (flatMap and map + filter/filterMap if you use `if` statements) respectively? It's a minor pain that they don't exist, but ultimately not something that I find is a huge deal. Besides I'm not actually a huge fan of those syntaxes. They force you to rewrite code in A-normal form, which means that wrapping monadic types can cause large rewrites to your original code.
I much prefer an approach like Scala's monadless (https://github.com/monadless/monadless), perhaps with additional syntactic markers indicating that this is a syntactic macro rather than a function, which does not force users to rewrite their code entirely in A-normal form. Kotlin has something similar to this (coroutines) which is what Arrow Fx uses, but again, like many other things in Kotlin, it's hard-coded to be only one thing and cannot be generalized to other monads. However, it's a nice middle ground for marking side effectful code and syntactically much nicer from my point of view than either do notation or for comprehensions.
3. Honestly pattern matching isn't a huge deal for me. Destructuring gets me a lot of what I want and the much bigger deal is exhaustive sum type checks, which Kotlin provides in its `when` declarations. Experimental inline types are fine with me. They're stable enough and I've never really regarded `AnyVal` in Scala to be any more stable (it fails in a bunch of corner cases to the point that some codebases entirely eschew it in their style guides). Opaque types in Scala 3 will hopefully fix this and Haskell's newtypes are great. The two main differences in type systems for day-to-day code are lack of higher-kinded types and lack of typeclasses. As Elm and F# demonstrate I don't think either are fatal blows.
Speaking of Scala 3 I really hope it goes well. I'm still a tad nervous about introducing indentation-based syntax since it seems like a needlessly divisive choice for little benefit, but we'll see how it goes.
I love Scala, and it's getting better year over year from all angles. It's also getting a little more opinionated. The upcoming version 3 removes long-standing warts and adds great features including sum types, intersection types, enums, and more. A huge thing is that it addresses binary compatibility issues, by allowing Scala 2 and 3 to use each other's libraries. Also, Scala has wonderful, super stable JavaScript support with Scala.js, and under development native support Scala Native.
Conversely, if you are developing a new language, focus on building a community and ecosystem around certain problems, even niche problems. That will give a consistent group of users that can build out the rest of the ecosystem slowly.
My dream would be for Rails to get good types or for a language with good types to get a Rails style framework. Maybe Sorbet is the solution, although I've seen enough confusing Ruby metaprogramming to be skeptical of that. Maybe someday Rust, Swift or Kotlin will get a mature web stack.
For instance, there's exactly one JWT library. To get a simple auth setup going, I'd need to connect this JWT library to my password setup, which I'm gonna need to design too, since I'm using a barebones password hashing lib. Now I need to write the token exchange and validation logic. Then manually put the token in the header/response, etc.
Compare that to Rails where I can install devise_token_auth and just have tokens out of the box. No fuss.
What Javascript framework are you using? Or did you just mean on the frontend? I figured from your including it you meant something backend, like Express, which is probably the most popular in that space, and fairly minimal, but super easy to grab the middleware pieces you want.
As, yeah, if you're looking for one giant "does all the things" framework, I don't know of any. Even the ones that started that way, reconfigured their projects to keep them optional (i.e., https://github.com/actix/actix-extras ) ...and honestly, I feel like most frameworks are moving in that direction. Even ones that want to be like Rails (i.e., Phoenix for Elixir) tend to be very intentional in making the included pieces fit the middleware pattern so you can swap them out easily.
So you may be staying with Rails for a while if that's important to you. I will say, comparing when I had to learn enough Rails to get something done, and having worked with Express, and Phoenix, and Gin, it's generally no harder to learn the pieces you need than it is picking up any new batteries included framework. Oftentimes the same web search that would turn up the one bit of syntax you need for the batteries included framework (i.e., "actix-web auth token") will turn up the most obvious choice of middleware you'd need for the middleware approach. And the usage will be of similar effort, once you've installed one.
Cloud Pubsub has HTTP and gRPC APIs, wonder why they didn’t use those? https://cloud.google.com/pubsub/docs/apis
"In common use" might mean commonly packaged for Linux distributions and not targetted for programmers, or driving multiple public websites that are not programming-related.
And then, when you have a ton of code, you have a lot of embedded knowledge and throwing it all away is tough.
Looking forward to the next installment to hear what is next.
My bet is Python - it has some types but more importantly a massive ecosystem, and I think Dark’s target market probably has a little Python knowledge (which would come in handy if they’re looking for open-source contributions down the line).
And while 3 fixed some footguns, e.g. 'a' < 5 now throws a TypeError and there's a clear distinction between str and bytes, others remain, e.g. if you reuse a generator it will silently be empty.
Looking at the repo[1] the languages tab shows:
OCaml 78.6% F# 12.2%
The F# hits are in a directory "fsharp-backend", and all in the last 3 weeks.
We also use F# for two react front ends with fable (F# to JS compiler). More new alternatives for using F# in the browser (FsBolero/Blazer) via web-assembly or running it server side like Phoenix live view also exist.
One issue we had with Fabel is how .NET DateTimeOffsets are handled on the Client side.
Static types aren't necessarily the best fit for distirbuted systems, network protocols, asynchrony etc.
Apart from the too-many-ports problem, I think the main remaining problem is that too many 3rd party libraries require Unix-isms to build, like shell scripts. This necessitates the presence of cygwin for build (but not at runtime). However the ongoing "dune-ification" of the OCaml universe should help fix this since dune can do everything directly from OCaml code. I'm really looking forward to being able to open a powershell window and type "git clone"; "dune build" and have everything just work.
I feel this is the single most important causative factor for language popularity.
Javascript may have much more bad parts to the language than say Haskell but it's far easier for a programmer to learn all the bad parts of javascript and avoid those bad parts then for him to learn all the good parts of haskell and use those good parts.
https://www.joelonsoftware.com/2006/09/01/language-wars/
FWIW I can see this igniting more arguments. And it is for sure interesting that he mentioned as Python the 0.5 that's on the edge, and Ruby on Rails that's a little further on the edge.
And Ruby on Rails turned out to be phenomentally successful for many billion dollar companies since 2006.
https://news.ycombinator.com/item?id=24279611
But that doesn't make Joel wrong. If you adjust 2006 to 2020, the same point can be made with different languages/platforms.
I know that typically on new projects there’s a long evaluation period where you decide what technology to use, along with lots of debates that include some crazy person actually wasting quite a lot of time evaluating Squeak and Lisp and OCaml and lots of other languages which are totally, truly brilliant programming languages worthy of great praise, but just don’t have the gigantic ecosystem you need around them if you want to develop web software.
These debates are enormously fun and a total and utter waste of time, because the bottom line is that there are three and a half platforms (C#, Java, PHP, and a half Python) that are all equally likely to make you successful, an infinity of platforms where you’re pretty much guaranteed to fail spectacularly when it’s too late to change anything (Lisp, ISAPI DLLs written in C, Perl), and a handful of platforms where The Jury Is Not In, So Why Take The Risk When Your Job Is On The Line? (Ruby on Rails).