74 comments

[ 3.2 ms ] story [ 156 ms ] thread
Is it my imagination, or has there been an upswing of interest in F# on HN recently?
Yeah, I blame it on version 5 (and dotnet) and the overall tooling improvements. It has really become an excellent and easy to pick up language, with not too many warts and a very good ecosystem.
I hate myself, 5 years ago I gave up dotnet to move to Java because it was more popular now I'm drowning in Spring beans and wish I never switched.
Ha, I'd be very interested in seeing a sociology study on Java. There's nothing about the language itself says you need to seize on half a dozen design patterns and beat them into the ground, but that's what every Java project I've ever seen looks like.
I'm also a fairly recent migrant from .NET to Java.

My sense is that the root cause of a lot of it is missing features in the Java language itself. There are a lot of cases where in C# I'd have used an extension method, or been able to lean more heavily on generic programming or Expression<T>, but, since Java has no good equivalents of these things, instead I have to break out a design pattern to accomplish the same task.

And, once you've cracked that seal, it's sort of an, "In for a penny, in for a pound," type situation. Next thing I know, I'm neck deep in it, and I don't even know how I got there.

"Design patterns are bug reports against your programming language." - Peter Norvig, https://norvig.com/design-patterns/
While I did just criticize Java for lacking some features, I genuinely don't agree with that Norvig quote.

It's true that, in a single dispatch language, I sometimes need to use the visitor pattern. And the visitor pattern is a little bit annoying to implement, it's true. But the implication of that Norvig quote is that every single language should implement multiple dispatch, and I simply cannot agree with that. And I suspect that a lot of people who like to invoke that sentiment would not prove to have the courage of their convictions when presented with a situation where Haskell needs a design pattern (sorry, "idiom") in order to work around the fact that it doesn't have dynamic dispatch at all, multiple or otherwise.

In general, it's OK for languages to be small and focused, or even to be large and multiparadigm but still draw the line at adding some features. That's not my criticism of Java. My criticism of Java is more along the lines of, Java's a kitchen sink language, and there's nothing wrong (aside from perhaps aesthetics) with being a kitchen sink language, but there are other kitchen sink languages that did a better job of cramming more random crap in more cleanly.

Tooling and marketing budget improvements I'd imagine
Tooling for sure. I would be shocked in the marketing budget increased just based on how many recent .NET changes recently exclude F# (e.g. source generators).
I have yet to learn it myself, but I get the idea that there is a huge amount of people who "shut up and get shit done" with it. Posts on HN tend to bring these people to the surface.

The interest has always been consistent, it's just not vocal.

it's always been bubbling over here
There was a wave of popularity in 2017 as well. I used to work on it full time back then, and enjoyed it a lot. The SQLProvider [0] and other type providers like it are super impressive!

[0] https://fsprojects.github.io/SQLProvider/

I've done a few little projects with the SQL provider. I worry about maintainability. You can't compile if the schema changes without you knowing about it.

I haven't run into any real problems as a result but it does make me nervous.

> You can't compile if the schema changes without you knowing about it.

There are those who would argue that making an incompatibility between the database schema and the code that interacts with it a compile-time error instead of a run-time error is a huge win for software quality.

I suppose there are situations where you simply can't have access to a database that mimics the structure of the production database during development. In which case, the SQL provider is definitely not the right tool for the job. But it might be worth looking into ways to change how you do things in order to make it the right tool for the job. A SQL database with an unpredictable schema makes me much more nervous.

You can save a snapshot of the schema into your repo.
> You can't compile if the schema changes without you knowing about it

That's why you don't normally compile against the production database. You version control the database schema and use it to create a local database to compile against. This way you can also test potential future migrations using branches.

Compiling against the production database should be done separately, as a kind of free integration test (usually in CI).

Maybe a combination of churn from Scala, growing popularity of .NET in general with .NET Core, lack of high-level stuff in Go, static typing coming to dynamic languages (especially JS), functional features getting everywhere. Lots of stuff is pointing at it in a way.
I’ve only played around with it on a few smaller projects, but it blew me away with the simplicity and expressiveness. I’ve used it a few times to make simple web based UIs for personal tooling and am looking forward to using it on something bigger.

Just one of the many reasons I find myself more and more excited by F#.

maybe rust contender on the frontend
Along with Fable highly recommend a look at Feliz. A joy to work with and its ViewEngine can also separately be used w/ Giraffe just for the syntax:

https://zaid-ajaj.github.io/Feliz/

https://github.com/Zaid-Ajaj/Feliz

For those interested in Svetle, Sutil is another thing to check out. Top of my head I can’t remember if it is based on or just inspired by Feliz.

https://github.com/davedawkins/Sutil

It's inspired heavily by Svelte itself, and borrows the Feliz DSL for building DOM. Feliz itself is a layer on top of React, Sutil is ground-up raw DOM building.
> optimized for happiness

I like React and I like F#. But I never understand this kind of marketing. It's not that I want things optimized for pain. But I guess I'm just most interested in the technical choices and actual performance in the domain over my own happiness?

I'd imagine the folks who take up these projects require f# everywhere in their lives to obtain happiness
Since the F# transpiles into JS at runtime it's really no different than, say, TS in terms of performance concerns.

Unless you meant something else there?

I mean that the marketing around "happiness" doesn't directly address any concerns that developers should have when evaluating a library. At best it's indirect if you assume that happiness means performance, but that's a big assumption.

So I'd rather marketing just say exactly what it's direct goals are rather than indirect ones like happiness.

> Since the F# transpiles into JS at runtime it's really no different than, say, TS in terms of performance concerns.

But as a particular point, this is hard to believe. TS tries to have no (or a minimal) runtime component whereas F# definitely must have a major runtime component.

Zaid Ajaj probably has had the biggest impact in establishing the Fable/F# ecosystem.

Besides his numerous repos, the Elmish book [1] is really awesome.

[1] https://zaid-ajaj.github.io/the-elmish-book/#/

Zaid Ajaj is prolific to say the least. I can't believe the number of F# tools he has created.
Does Feliz work with Fable?
Feliz is an F# library designed to be used with Fable
I don't know a ton about Fable, but they recently wrapped Google's Lit to allow building functional templating and web components in it: https://fable.io/Fable.Lit/

Seems like a neat project.

Fable is a brilliant project.

It doesn't try to hide the fact that when you write a Fable program, you're fundamentally in the JS ecosystem. This can be a bit of a shift if you're used to doing stuff in .NET, but it means that everything will more or less work with your code.

It also comes with a few tools/utilities to ease the F#-JS bridge you're on.

> it means that everything will more or less work with your code

So you can call any .NET library?

by "everything" I think GP is referring to the JS ecosystem
Ok. Wouldn't it make more sense to compile .NET bytecode to JS?
I've never dealt much with transpiling to JS, but I would guess that it's a lot easier to do it from the AST or some other higher-level intermediate representation than it is from CIL instructions. That way you don't have to fuss with programmatically figuring out what stack manipulation instructions are trying to accomplish or any other target-specific noise like that.
Not necessarily. Aside from being more technically challenging, it's more of a "what are our goals" kind of question. Do you want to give F# developers who are afraid of javascript a way to avoid it altogether, or a way to bring those developers into the javascript ecosystem?
Fable has easy interop with JS libraries because it compiles to JS.
Not quite, it's a middle ground, https://fable.io/docs/dotnet/compatibility.html Blazor, (or Bolero as an F# friendly extension) compiles to WASM to run any .Net library.

In practice you occasionally find something that doesn't quite work, for the most part things related to IO and the TPL but I occasionally run into some more niche functionality with Date & time for example.

You can use most of the core F# library and some non fable centric libraries (e.g. FSharp.UMX of FSToolkit.ErrorHandling) work just fine.

I find it mostly a non issue, as any IO you want to go through the browser API instead or it's not supported. It didn't take take long at all to easily slip into the "I'm in the browser now" mindset.

Maybe I'm biased, but I'm often skeptical of the benefits of projects like this that translate a language which normally targets runtime A, into another language that targets a wildly different runtime B (usually JS or WASM)

(Disclaimer that I'm not talking about compiling runtime-less languages for different platforms)

It just seems like it's going to be a very leaky abstraction, like there will be tons of corner cases where some behavior isn't exactly the same and other behavior isn't quite possible, which will cause libraries and in-house code alike to break in weird ways. I know that ClojureScript, for example, has several caveats relative to Clojure; and most of these translators are not nearly as mainstream (and so don't get nearly as much maintenance attention) as ClojureScript does. And then there's the messy question of interacting with the host system, dropping down into the host language for certain things, etc.

I haven't really worked with these kinds of systems so I may be way off, but from the outside it seems like a huge increase in complexity just so you get to stay in a familiar language

I'm only passingly familiar myself, so I might be off here-- but F# already has that sort of feel and experience, where it has some of its own ways of doing things despite integrating into the .NET runtime.

In other words, the language designers and users are already used to understanding corner cases and integrations into ecosystems that are different.

Interesting point! I haven't used F# so I assumed it was pretty "native" to the .NET runtime

Still- I have to wonder about things like concurrency, which is perhaps the most obvious limitation of targeting the JS runtime

There are some caveats they outline for the usual async stuff[1], but at a cursory glance, it's not that bad. Thanks to F#'s computation expressions, .NET async code and Fable.Promise code look almost identical.[2]

1: https://fable.io/docs/dotnet/compatibility.html#Caveats-II

2: https://stackoverflow.com/a/54451205/4099287

My suggestion is to use async in F# code by default. Use task for certain heavy server code on .NET and promise for JS interaction, where required. Async mostly works everywhere.
Is there some sort of (for lack of a better term) Monad Transformer that lets you mix `async` and `promise` in the same computation expression?
Computation Expressions are implemented using a class under the hood so you could probably build one with appropriate overloads.

There’s a function that Fable provides to turn a promise into an async, so mix and match isn’t a huge challenge anyway.

Yeah, I'm just trying to avoid having to throw the adapters on each line. Just like fable supports any thenable, it'd be cool if async/task could be treated as a thenable.
F# is certainly a .NET language depending on the lens with which you view it. The object system and lots of its semantics are the way they are because that's how .NET works. However, a surprisingly large amount of this stuff is translatable and makes sense with a JS runtime instead. The fable authors have managed to thread the needle just right so that it more or less "feels right".
People would rather write a compiler than write js. Ridiculous.
Er, yes? Compilers are brilliant fun.
It just shows how terrible JS as a language itself.
That doesn’t strike me as being ridiculous at all
- Shared language for server and client has its advantages.

- F# has some significant advantages over JavaScript (and TypeScript).

I've written quite a lot of F# code for the web over the last few years. Most of the time things just work as long as you don't use overly specific .NET BCL stuff.

We had issues with Dates. Implementing a custom 'ZonedDateTime' type with native implementations for both target platforms (JS and .NET) was straight forward.

Other than that there are a few gotchas when doing 'advanced' stuff:

- Fable does not support 'Array2D', 'Array3D', ... - Fable allows the use of ints but you have to know that at runtime they all are floats. - Fable does not support FSharp Quotations. - Fable does not support the full reflection API.

More here: https://fable.io/docs/dotnet/compatibility.html

Would definitely recommend F# for both backend and frontend. It's been a joy.

ClojureScript is in a weird boat because of a constant background tension in goals of the Clojure community, where on the one hand Clojure proclaims that it is a "hosted language" and as such there shouldn't be an expectation of consistent semantics across different hosts, but on the other hand devs really like consistent semantics if the language doesn't change. So the Clojure community has kind of settled on cljc files as an indication of "works on all hosts in the way you would expect" even though that's not any part of the guarantee that a cljc file provides in any documentation anywhere (it gives you reader conditionals and that's it). Basically ClojureScript isn't really sure whether those "caveats" are bugs or features and neither is the Clojure community at large.

More broadly they aren't sure whether causing "libraries and in-house code alike to break in weird ways" on different hosts is a bad thing (some folks would probably take issue with the word "break").

On the whole however these projects can go very well. I've seen e.g. ScalaJS and Scala backend projects work very well. They're a lot less leaky than you would think. The vast majority of code just works. In particular, there are clean lines between what is supported by the abstraction and what isn't, and the latter doesn't affect the former, so in that sense the abstraction is pretty tight and not leaky. And the major win isn't so much the familiar language part as it is the ability to share code and definitions. It's really powerful to change a line somewhere deep in your backend and have all the places you need to make a change all the way out to the frontend UI automatically laid out for you.

That said the big misconception, and where the abstraction is incomplete if not leaky, is that these languages allow you to disregard the runtimes of secondary targets. No compile-to-JS language I know of has successfully done away with the need to understand the HTML/CSS/Javascript stack. And that indeed is extra complexity that should give pause to developers.

But RE "cause libraries and in-house code alike to break in weird ways" generally does not seem to be the case for these compile-to-JS languages.

The reason I'm bullish on Fable (and Feliz) is that in my experience the challenge in writing a modern web application is dealing with state, especially when API calls are involved. With Fable I save a lot of time (and bugs!) in that, so I'm okay with spending more time integrating with javascript libraries.

In my experience you can achieve the same functionalities with a lot less complexity in Fable + Feliz than the equivalent typescript stack.

Since we're in here talking about all kinds of F# goodies, has anyone spent time with https://wiz.run/?
Story on Wiz is since I’m a growth engineer I tend to think about usage on a more psychological level (optimizing for reduced friction and simpler onboarding). I found that most libs in F# have the .net APIs right in your face so users need to learn both. (E.g. I would define route handlers in the http library but if I want to change the server port I have to look at dotnet docs.)

If F# is to be successful, I think more attention needs to be made to build abstractions on .net APIs.

I briefly poked around the source code. I would pick Suave over Wiz since Suave has an immutable data model internally.

If for whatever reason Suave is not fast enough, Giraffe can be fine tuned. However, it also exposes much of the underlying ASP.NET stuff.

I love the website for Wiz though!

yeah wiz is still a work in progress. Its mainly just trying to be a simpler API wrapper for asp.net

Would challenge the notion of immutability or extensibility being a primary motive for using a library. Would like to see UX (DX more specifically) start to take more of a front seat in F#, since its one of the more neglected parameters of success in the ecosystem. Extensibility can be added, UX is more difficult to retroactively account for.

If Wiz only influences other libraries to become simpler for new users then I will consider it a success.

I just want more eng teams to use F# or Ocaml without having to justify why you aren't using python/node.js.

I admire and share this goal!

However, if you translated the Wiz examples to Suave, would they be any more difficult to read?

I think the biggest issue is Suave’s docs etc.

What's even more exciting is there is work underway to allow Fable to target Python instead: https://github.com/fable-compiler/Fable/issues/2339

These things are bizarre when you think about the tech stack they lead to, but undeniably still really useful in our unfortunate real world. I think of F# as 'the pragmatist's functional programming language', and projects like this follow that mindset.

Is it named Fable due to its relation to the Fable series which Microsoft publishes?
I believe it is a portmanteau of "F#" and "Babel" the latter being a common framework for es6+ javascript compilation [1].

[1] https://babeljs.io/

Debating F# for OSS project I'm about to start but the asp.net C# with blazor story(though I need support for loading blazor code at runtime for plugins and can't get a straight answer on that) is very compelling.

As to Fable.. I would want to use it on Vuejs perhaps if I can't do blazor, but also with something like mobx and NOT Elmish. I despise redux and it just looks gross. I want the Mobx "magic" for this boring view update stuff.

You can use Fable + Feliz + React hooks as an alternative to Elmish.
Is Elmish no good? I was hoping to look into it soon.
I'm guessing it's fine for people who like Elm or the Redux pattern. I don't like that pattern as a go-to solution.
How close are the semantics to F#? For example OCaml has bucklescript which produces reasonable looking JavaScript but doesn’t always follow the language semantics, and js_of_ocaml which produces horrid JavaScript (it compiles from 32-bit OCaml bytecode to js, and fakes the OCaml memory layout of objects by using arrays with their first element being the tag of the object, so e.g. a float is represented in js as [<magic number>, <the actual float>]).

Similarly, Common Lisp has something called parenscript which compiles a lispy syntax to js but e.g. uses JavaScript arrays for its lists (so you don’t have a cons function).

Fable produces reasonable JavaScript and makes some semantic concessions towards JS. For example, ints are compiled to JS floats. You still get full compile-time type checking.

For shared code it can be wise to maintain tests that can run on both .NET and Node.js.