Hacker News new | past | comments | ask | show | jobs | submit login
The Early History of F# [pdf] (acm.org)
138 points by strangecasts on June 13, 2020 | hide | past | favorite | 77 comments



We, at Chaldal (YC S15, chaldal.tech), use F# in production. Most of our backend projects are in F#, the last one is being migrated over slowly from C#. Our new frontend projects are also in F# (using Fable).

I want to share some insights of using F# with the community. We started from a C# codebase, and realized that a better language can help weed out most bugs in our system. And yes, it works. Pretty much all bugs we face these days are parts where the F# world touches something non-F#, like .NET and other libraries written for C#, where interaction (like nulls) is not well-defined. We've taken Scott Wlaschin's (fsharpforfunandprofit.com) teachings to heart, and we have a giant banner in our office that reads "Make illegal states unrepresentable".

It took a bit of learning for everyone to jump in, but our dev team has loved the experience as the language is a pleasure to use; when they need to go back to write C# or TypeScript code, a lot of these learnings transfer. People just become better programmers (as is true with learning any functional language).

To get all the benefits of F#, you must adopt the whole paradigm. While F# allows C#-like OOP, and while this can be an initial stepping stone in the path towards F#, you must go all the way. If you simply do OOP, the trade-offs aren't worth it, IMO, as F# is a functional-first language, and the OOP is mostly provided for interop with the rest of .NET.

IDE support has been janky in the past, but its improving. Latest VS 2019 is pretty good, and JetBrains Rider works pretty well on the Mac.


I get the "Make illegal states unrepresentable" in theory but how do you do it actually for nontrivial preconditions like "this list must be sorted"? (as a precond for a binary search on a vector, for example).

OOP is fine with functional if you make it immutable too, so I don't see the problem (I've not really got a problem with mutable OOP, or state generally, if it's done carefully).


> how do you do it actually for nontrivial preconditions like "this list must be sorted"?

This depends on your data model, of course. But for a list of integers you could do the following:

1. Have a unique data type that is the list of Deltas, plus the initial element (So for instance the list [5, 3, 6] would be encoded as (3, [2, 1]).

2. Provide a sort function that creates such a sorted list.

3. Make the sorted list your input parameter.

This is obviously oversimplified, but I hope you get the idea.

A cheaper alternative is to

1. Make an opaque datatype "sorted list", together with a function that translates this type to a normal list. Implemen this type just as a list, but keep the implementation private.

2. Provide a function sort, that is the only function that can yield that type.

3. Demand that type as input.


Encapsulation aided by types and module interfaces.

In Reason/OCaml, you can create a module interface (.rei or .mli) which makes the type opaque to functions outside the module. So instead of saying `type t('a) = list('a)`, it'll just say `type t`.

The compiler relies on this type information to decide whether functions are well-typed. So if an external module presumes to know the structure of the type and does an operation on it, it becomes a type error because the compiler simply doesn't know about the actual type. This is similar to encapsulation in OO where we don't expose a field to the outside world, but here it is done by virtue of the type signature itself, which I found to be a more powerful guarantee of encapsulation.

The module can then expose functions like `append` which would be the only way you can manipulate the list. This function in-turn can ensure the postcondition, guaranteeing that the list stays sorted. At the point in which we want to use the list for functions not supported by our SortedList module, we can turn it into a regular list with a `to_list` function. Since the underlying type is already a list, the operation is virtually free. The function would look like `let to_list = (xs: t('a)) => (xs: list('a))`. It is an identity function, with just the type changed for the compiler.

Similar rules about `append` applies to the constructor: we can create a new `SortedList` only with a `SortedList.make` which can ensure the postcondition. There will be no other way, thanks to the type being hidden, to create a value of the `SortedList.t` type.


I would use the “smart constructor” pattern for this: https://draptik.github.io/posts/2020/02/10/fsharp-smart-cons...

Things like that require runtime logic, there’s no way around that.


It's idiomatic to use "opaque types" to achieve this in F#. In practice it is actually pretty analogous to the concept of a "constructor" in OOP.


The difference between doing functional programming in C# vs doing functional programming in F# is subtle but important. A true FP language like F# will not allow code that introduces certain classes of errors FP is designed to prevent. Whereas in C#, a developer needs to be really careful not to introduce those errors even when following FP style. C# will always allow mutable states, null values, global variables, to name a few.


As other replies to this comment describe, we make heavy use of opaque types where runtime enforcement of safety is need. Some examples: PositiveFloat, PositiveDecimal, NonEmptyMap, NonEmptySet, NonEmptyList, KeyedSet.


> While F# allows C#-like OOP, and while this can be an initial stepping stone in the path towards F#, you must go all the way.

I'd be less charitable and say that OOP in F# is a tedious, verbose mess. Functional programming in F# is extremely concise, at the expense of OOP. I wrote a parser/lexer in F#, to be used from C#, and ended up rewriting it in C# instead of dealing with the interop. I'm hoping that some day, C# will eventually have F#'s pattern matching power. They're slowly introducing more and more features to do it, and there's no major technical reason why C# wouldn't be able to.


The way we do it at $WORK is to do a proper F# API and then expose a fluent C# API that wraps it. Our APIs are usually based on constructing descriptions - which a fluent API can do just as well as a "native" F# one - and then we provide a very few functions which interpret those descriptions. The hard interop is in constructing the descriptions, because C# likes objects to look very different from F#; we just give up and expose two separate APIs for constructing the F# objects.


Agreed. You can't completely avoid it as we're running on a framework (and libraries) that's based on C#, but we architect our code such that these messy edges that interface with the outside world are implemented once, creating a nice walled garden for the rest of the F# code inside.


> "Make illegal states unrepresentable" > (as is true with learning any functional language)

...unless you are using a language where types are not part of the contract and not used in dispatching, such as Elixir.

I have mixed feelings about that because, on the other hand, in runs on the BEAM VM and as long as lack of typing leads to a proper crash, you may be OK as long as your code is written correctly.


Type providers were the biggest innovation I learned from F#. Type providers are a way to have static typing determined from an external source, like a CSV file or SQL database.

I ended-up using a similar trick (with different syntax) in my own language, Empirical. I needed a way to infer a Dataframe from a file while in a REPL. It wasn't until I read the MSR paper that I realized I could do it entirely in the compiler without creating separate logic for the interactive users.

https://dl.acm.org/doi/10.1145/2908080.2908115


Will you please share your insight? What are Type Providers? What is their advantage over, say, naively dumping type definitions from pre-processing a CSV file?


The user could certainly dump a type definition, but for users who just want to point to some data in one go, the F# type provider[0] works with just:

    CsvProvider<"trades.csv">.Load("trades.csv")
I personally didn't like having to list the file twice, so in Empirical[1] it's just:

    load$("trades.csv")
My current version requires the dollar sign to tell the compiler that the parameter will be static (known at compile time). I'm planning on an updated version[2] that will eliminate the dollar from the function call:

    load("trades.csv")
Basically, it means that users can just load the file like in a dynamically typed language, except that Empirical is statically typed.

[0] https://fsharp.github.io/FSharp.Data/library/CsvProvider.htm...

[1] https://www.empirical-soft.com

[2] https://github.com/empirical-soft/empirical-lang/issues/23


https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/typ...

It’s not perfect though. When I tried to use them I ended up with problem so I parsed the data in more traditional way.


Published as part of the History of Programming Languages (HOPL) IV proceedings[1], which also include articles about C++, D, Groovy, and JavaScript, and a few other languages.

[1] https://dl.acm.org/toc/pacmpl/2020/4/HOPL


I've recently become interested in F# after I saw its use in a bunch of videos by Scott Walashin. I came across his talks, and subsequently his blog, as I'm getting more into functional programming and he has some awesome content.

I use Javascript/Typescript professionally and I really like both of them. F# though has a minimalism in its design that is very appealing. Code I write in F# feels a lot less cluttered as opposed to the same function in Javascript/Typescript. This minimalism seems to be part of the language design with some following specific examples.

- `let` is used to declare variables and functions.

- The last statement in a function is implicitly the return value.

- The type safety/mechanism is great and seems to be extremely similar to Typescript's so its easy to pick up for me (I'm not sure which came first or if there's a relationship but it certainly wouldn't surprise me).

This along with some classic functional ideas that are built in like immutability by default, automatic currying of functions etc all allow for a language that seems be made to get out of the way. That's a very compelling idea for a language design to me that I previously haven't seen.

Because functions and variables are both declared with let, perceiving functions as data kind of finally clicked for me since I first heard that concept 3 or so years ago. Another feature was to not require a semicolon as a line terminator and instead use it for another meaning as a separator. While JS doesn't require semicolons at the end of a line, its reserved for that purpose.

Overall, I find F# to be a refreshing language (from javascript, python, java and even others like Rust or Go but I haven't used those as much so don't want to make bigger claims) and I'm glad to see a post like this on HN almost like validation of a community behind the language. I'd be curious to get a feel for some companies that actively use it as well.

I also love that there's a compiler for F# to Javascript so I can use it to build front end code as I learn the language. Its pretty incredible we can do cross compilation of high level languages like this and with others like Rust and Go to wasm etc

Edit: formatting of bullet points


Scott's talks about F# are exceptional, and quite compelling for "line of business" developers (as opposed to language enthusiasts) - something functional programming advocacy often misses in my opinion. This is also something that his site (https://fsharpforfunandprofit.com) does a good job of.

On a slight tangent, you might enjoy the slides from Scott on a different topic - model checking with TLA+ - from NDC Oslo this week:

https://www.slideshare.net/ScottWlaschin/tla-for-programmers...


Oh that's awesome. I first came across TLA+ (probably also through HN) about 6 months ago and recognized Leslie Lamport's name. The concept was very interesting but I had trouble getting to a good enough understanding of TLA+ to be able to use it. I'll look to see if I can find the associated NDC talk on youtube but his slides are also great. Thanks for sharing.

He does an incredible job conveying the ideas and intuition behind a lot of the terms and concepts for a given subject.


I don't think the talks have been published yet (they were only given ~2-3 days ago), but agreed on the slides being useful!


OCaml also has a compiler to JavaScript - BuckleScript[1]. There is also js_of_ocaml[2], jsoo in short. Though I personally hope they will go with the WebAssembly route[3] and support it in the mainstream compiler out of the box.

[1] https://bucklescript.github.io/

[2] https://ocsigen.org/js_of_ocaml/3.6.0/manual/overview

[3] https://discuss.ocaml.org/t/state-of-ocaml-and-web-assembly/...


Wow this is a great comment about a new-to-FP viewpoint on F#. I came to it after I'd already experimented with Haskell and OCaml, used Rust a lot, and been programming with Lisps for years - and I was really impressed with it nevertheless!

I really love the ML-family type systems. They're really great aides to programming, and makes planning really nice. F# has that, plus that usability from C# and TS. It's also, like you said, got a really beautiful syntax IMHO.

I keep coming back to Rust though, because Rust is more general purpose, more performant, and applies better to the stuff I like to work on for my hobby projects. I'd love to use F# more but can't find a reason to at the moment.

TypeScript, Python and Rust have become my comfort languages, but I like Rust the best, because it's got a lot of really nice features from ML languages and a powerful and safe type system.


We use F# to build fairly complex portfolio management apps. It's a great match for F# to Javascript transpilers (Fable, WebSharper). It makes it easy to share functions and types between UI and backend and to have powerful type safety. I.e. you change your DB data type and compiler informs you where in the UI you need to make appropriate changes.

This works great with "makes illegal states unrepresentable" approach. It helps to reduce a need for boring unit tests and lets you focus more on expressing domain in code directly.


I love this idea too. Is it possible to create a type (in F# or Typescript) to represent an idea like greaterThan2? A value whose type is greaterThan2 would have the obvious constraint the value is always bigger than 2. Having the compiler check such a condition would be awesome.

I kind of can do it for Strings by doing something like this

  type ValidStrings = 'name' | age | 'dob';

The easy way around this is a function to determine this

  function greaterThan2(num) return num > 2;
but it'd be cool to express this level of dynamism as a type.


As others mentioned - using smart constructor technique, but not directly as F# has no dependend type capability.

Smart constructor technique works well with 'parse, don't validate' approach [0]. You can push type construction to the boundries of your system so that you can work on a domain code with more precise types. It's not always so rosy however as too much types can become a burden.

[0] https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...


This is called dependent typing, and F# doesn't have it as far as I know. There are other ML-family languages that are capable of it however: Liquid Haskell and Idris are the two I can recall off the top of my head.


in F#, yes, in TypeScript, not really. The technique involves using a combo of an opaque nominal type, and a smart constructor:

    (* GreaterThanTwo.fsi *)

    type t

    val make : int -> t option
    val to_int : t -> int

    (* GreaterThanTwo.fs *)

    type t = Value of int

    let make int = if int > 2 then Some (Value int) else None
    let to_int (Value int) = int
Now, `GreaterThanTwo.make x` gives you a value of type `GreaterThanTwo.t option`, meaning it can be `Some` value or `None`. If it's `Some` value, that means it really does contain an int greater than two and you can get that int using `GreaterThanTwo.to_int`.


This is so true. It's why I love ML-languages: they help me refactor, the help me logically structure my core (through its data types) even before I start, and it helps catch illegal states (if I use the types right) so that unit tests aren't necessary!


F# has the nicest syntax of any language IMO, it's a shame it's not got a larger mindshare.


High praise and interesting to hear this as well from someone else. I feel like I'm on the right track with my analysis in my main comment.

I saw in a different HN post a few weeks ago that jet.com heavily used F# but obviously that's no more.

If a company were using F# and I were looking to switch and all the other stuff was a good match, the language choice would be a big pull for me.

While I use and am learning F# for a side project, there's no substitute for working with one professionally and shipping production code with it. I wish it'd be possible to convince my engineering group to use it.


If your engineering group already uses C# then it might not be such a difficult sell - the two languages are highly interoperable, so you may be able to carve out a particular greenfield project and write it in F# instead of C#. Note, though, that we've found it necessary on project `FooBar` to create specific `FooBar.CSharp` wrapper libraries that C# users can consume; this is because idiomatic API design looks pretty different between F# and C#.

(If you're interested in quant finance in London, I work at G-Research which uses F#; I write almost solely in F# there. You can contact me privately if you like at patrick+gresearch@patrickstevens.co.uk.)


Rust is actually the one other language I'm interested in learning now and is on my list. I have a feeling I'll be in a similar mindset to you once I learn and get some experience with it. I'm interested to compare its syntax to F# as well since to me the F# syntax and design is what I find most compelling.

More broadly there seems to be a big push for updated (perhaps even modern) versions of established languages and I think this is an incredible development to witness.

Rust, Go, Typescript, Scala, Kotlin are all the rage and seem to replace C/C++, javascript, and java. Others might exist for either side as well, this is just off the top of my head. From what I see and read as well functional programming seems to be a lot more popular and a lot of these languages are either built for it or with a good base of support for it. It'd be really interesting to have some hard data to know if this is accurate or just my perception.

My only other exposure to functional programming was an undergrad class required for a CS major (thank gosh it was a requirement too). It used Scheme for the language and Structure and Interpretation of Computer programs for the text book[1]. Looking back, this was the most useful class I took and the ideas have stuck and continue to inform me today. Its the one text book I've gone back to on occasion just to take a look and I've found some versions of it for JS and F# even on github which I use to help learn the language or review the concepts in languages I'm more familiar with.

[1] https://mitpress.mit.edu/sites/default/files/sicp/full-text/...


I need to give F# a try. Really love the ML style type systems as well, I've played around quite a bit with SML and OCaml, and have been using Rust as my "daily driver" for almost 3 years. Rust definitely feels like the most practical of the MLs


You can use F# in WebAssembly, too, via Bolero[0].

[0] https://fsbolero.io/


There is a lot to like about F#, but I honestly prefer Typescript.

I feel like it really has hit the sweet spot between FP and OOP. And while I do tend to prefer the more terse ML syntax of F#, Typescript is pretty close and, frankly, offers a more ergonomic experience in your editor (F# syntax highlighting in every editor I have used is woefully underwhelming).

Add to the above the structural typing vs nominal and Typescript really does offer an immensely rich and flexible paradigm. The only feature I'd really like to see better support for in Typescript is currying/partial application These can be approximated in TS using it's incredibly powerful type system (dependent types using "extends" combined with inference using "infer"), but newer versions will throw an exception when the compiler sees an infinitely recursive type.


re: relationship to TypeScript, I'm interested in which parts seem similar between the two to you. TypeScript is primarily structurally typed while F# is primarily nominal.


Sure thing, I'm still pretty new at F# but have a good handle on Typescript since I get to use it at work.

The three main things I find similar are the syntax, ability to compose new types from other types, and the ability to limit types to certain options. Both support | as an OR and support an AND operation. F# does a bit more allowing the product of types though.

Syntax Example:

Typescript

---------------

type Check = {

    amount: number;

    account: number;

    ...
}

F#

--------------

type Check = {

    account: int; 

    amount: int
}

I think I need to become a bit more familiar though with F# to understand the differences better. I hadn't yet heard the term nominal to describe a type system before so I'll look into it further. Perhaps I'm focusing more on the syntax than is warranted...

Completely separately, how does one properly format on HN? Is it possible? Is there a doc somewhere? I'm fairly new to commenting...


Formatting is rudimentary but it exists: https://news.ycombinator.com/formatdoc

Your example is actually a really good one for discussing these differences, and the syntax is indeed similar. That TypeScript code is introducing a "type alias," which is a special name for another type, rather than a new type of itself. (https://www.typescriptlang.org/docs/handbook/advanced-types....)

This is a concept that F# does not have, because F# doesn't support structural typing. In TypeScript, you could just as well specify that a function returns

{ amount: number; account: number; }

directly, and it would be equally compatible with anything expecting a "Check," because Check is just a shorthand for that object type. I can't easily declare that a function accepts { account: int; amount: int; } in F#, I have to declare that it accepts "Check." That is the main difference between TS and F# here, and incidentally also the most basic difference between structural and nominal type systems.

I'm not sure why the syntax is similar though, F#'s is based on OCaml's record syntax while TypeScript's is really just a JavaScript object literal but with semicolons instead of commas. I believe it supports commas too. It may be genuine coincidence, or the designers of TS may know of the ML family, which is highly influential among language designers and implementers.


F# does have type alias.

It takes the form

type newname = oldname

You can do things like

type LoadReceipt = RecordId -> Record

let sqlLoad: LoadReceipt = implementation

let noSqlLoad: LoadReceipt = implementation

You can use it like an interface, and accept loadReceipt functions for anyone who needs to load receipts... Then partially apply a SQL version, noSQL version etc.

I use this in my code.


I know, but in TypeScript the compatibility is structural.


Thanks for the formatting link, exactly what I was looking for to format code a bit better.

That actually helps me understand some F# code I wrote last weekend as well regarding construction of objects of a certain type...

I didn't know about the difference between nominal vs structural types but your explanataion makes sense. It's the subtle distinction between a type vs a type alias that you pointed out.

I personally use type aliases in typescript over interfaces, I think they're a more natural fit for JS than interfaces but that's a separate conversation :)

Language design is really interesting especially the choices designers make. I feel like they must all pull at least in part from each other and would be curious to explore any relationships but if its total coincidence that's pretty incredible too.


> I personally use type aliases in typescript over interfaces, I think they're a more natural fit for JS than interfaces but that's a separate conversation :)

The TypeScript docs recommend the opposite, preferring interfaces. Interfaces are also structurally typed in TypeScript though; the major differences are that interfaces can only be used to declare object types: https://microsoft.github.io/TypeScript-New-Handbook/everythi...


I don't see that recommendation on the page...

The fact that interfaces are limited to typing objects but type aliases can also be used for functions is an easy +1 to type aliases.

My view is that declaration merging is a bit of an anti pattern that could cause trouble if two different people write an interface with the same name but to represent different data... now they get merged incorrectly. A type alias forces more explicit behavior where composition must be done by creating a new type alias as the union of two or more other types

  type Foo = Bar & Baz
Edit: found the page, its here https://www.typescriptlang.org/docs/handbook/advanced-types....

Yeah, not sure I agree yet but I could be persuaded... I don't think type aliases prevent extension, they just force it to be explicitly declared.


I think it’s a +1 to using both depending on where they fit. I use type aliases for defining unions and intersections and such and interfaces for everything else, personally.

And that’s an intersection you’re describing there, not a union. Interfaces are explicit about extension as well, they use the “extends” keyword.


Whoops, yes that is indeed a union not an intersection. Thanks for the correction. I'm still getting used to the terminology.

The explicitness I was referencing is around the declaration merging. Its automatic so unless there's a conflict between two interfaces with the same name but with different properties, a dev wouldn't know that it was being done.

I agree with using what makes sense for the scenario. I prefer type aliases for functions and interfaces for classes and that seems to work out pretty well so far.


You can do anonymous records in F# - you don't have to explicitly give a name to your record types - but structural typing solves similar problems to those solved by interfaces, and F# thoroughly supports interfaces, so I imagine it wasn't thought necessary to make the type system any more structural.


You can use type aliases in f# and use them like interfaces.


Sure, and so you can in OCaml and Haskell too. The compatibility will still be determined by the name you give that type and not its structure.


Anonymous record?


Doesn't support structural subtyping either


The original OCaml is more interesting. It has language flexibility F# lacks, gets new features every year, crossplatform, and compiles into the native code, thus faster. Once Multicore OCaml [1] project is finished the major painpoint of it will go away.

[1] https://discuss.ocaml.org/t/multicore-ocaml-may-2020-update/...


I love OCaml. F#, if you squint, is kind of philosophically like Clojure for the CLR: it leverages a heavily object-oriented but nonetheless very powerful runtime to allow developers to create strongly-typed functional programs that interoperate with a massive array of first- and third-party libraries that already exist.

I wouldn't go so far as to say OCaml is more interesting. F# has things like computation expressions, async, units of measure, and type providers, none of which are trivial (or exist in OCaml), several of which are incredibly influential, and all of which are interesting. The long slog towards multicore for the OCaml community has been kind of embarrassing, with Haskell having offered arguably the best concurrent and parallel programming support in all of software development for years and years now.

I will also note that F# gets new features fairly frequently as well, is cross-platform, and is extremely fast by virtue of running on the CLR, which I would not underestimate: https://benchmarksgame-team.pages.debian.net/benchmarksgame/...

The features that it is missing from OCaml are admittedly pretty major, though. Row polymorphic records and the structurally typed object system are missed, not to mention rich modules and functors.


OCaml has the equivalent of computation expressions since 2018: http://jobjo.github.io/2019/04/24/ocaml-has-some-new-shiny-s...

It definitely has async: https://github.com/ocsigen/lwt

It doesn't have units of measure but due to strong abstraction properties of modules we can fairly easily roll abstract types.

It doesn't have type providers but it does have plugins for type-directed derivation of JSON codecs, equality, comparison, printing, etc.

> The long slog towards multicore for the OCaml community has been kind of embarrassing

IMHO it's been refreshing to see the pushback against 'multicore at all costs'. There is a benefit to taking the time to do things the right way. OCaml is a 30+ year-old language with an even longer ML heritage. It's going to be around for a while; it doesn't need to rush into a sub-par implementation. Meanwhile, people are writing highly concurrent, multi-threaded applications with it right now, and using multiple processes just fine for parallel computing.


I’m glad to see that the language is moving forward in these areas, but I don’t think you understand how F# implements units of measure. It’s not just another data type, they’re fully inferred and checked and work with generics: https://stackoverflow.com/questions/21538563/can-f-units-of-...

Type providers are a native feature, no plugins required. I have no animosity towards OCaml (in syntax and semantics, I prefer it!) but this is a bit of a stretch.

I don’t think anyone would say Haskell does multicore the “wrong” way, and OCaml as you say has had decades to solve these problems. I love the language but the community is very small and the language evolution has been extremely conservative for the most part.


> I don’t think you understand how F# implements units of measure. It’s not just another data type, they’re fully inferred and checked and work with generics

Yes, units of measure are really cool, and I understand they support dimensional analysis out of the box. It's quite unique, and I fully agree that abstract types don't fully replace them. Just saying that that's what people do in OCaml.

> I don’t think anyone would say Haskell does multicore the “wrong” way

And it certainly is not something that I said.

> OCaml as you say has had decades to solve these problems.

The 'problems' being the lack of multicore support? As I said, this is not really stopping people from getting real work done in industry–see everyone using OCaml currently in highly concurrent and multi-process parallelized applications, or in fact even people using NodeJS, Python, Ruby.

> the community is very small

Correct, but it is definitely growing.

> and the language evolution has been extremely conservative for the most part.

Not so correct. There have been massive amounts of changes in the last few years: https://www.ocamlpro.com/2019/09/20/a-look-back-on-ocaml/

And this is not even taking into account the features which OCaml had before then beyond F#:

- Polymorphic variant types - Structural subtyped object types - Named arguments - Optional arguments - Functors


F# has named and optional arguments and I named everything else as real differences in my initial comment in this thread.

I’ve interviewed with Jane Street and gotten an offer on the basis of my OCaml and F# skills. I understand where these ecosystems differ. I rest my case.


From https://docs.microsoft.com/en-us/dotnet/fsharp/language-refe...

> Named arguments are allowed only for methods, not for let-bound functions, function values, or lambda expressions.

> ...

> Optional parameters are permitted only on members, not on functions created by using let bindings.


I concede that point.


Thanks for the benchmarks link! This is definitely interesting and worth investigating.


> Once Multicore OCaml [1] project is finished the major painpoint of it will go away.

The major pain points are the tools. I tried setting everything up two years ago, or so, and I couldn't do it. While the language might be good (modules) everything else isn't.


f# also has language flexibility that ocaml lacks.

and ocaml isn’t always faster


Always love seeing a good rivalry between siblings.


Is this competition?. F# has intentional limitations due to the decision to work on CLR.


F# is the language that I keep coming back to. And as the paper notes, it has kept up well with the rapidly changing landscape of .NET Core and newer defacto standards introduced by modern C#.


It had a very rocky start in the .NET Core world thanks to Microsoft's lack of interest in supporting F#. It lagged behind C# in significant ways and continues to get very few resources behind it.


Actually Microsoft has quite a respectable team of developers dedicated to F#. It is true development suffered because the Roslyn compiler that MS invested a lot of effort into cannot be made to work for the language. F# has been made compatible with surrounding Roslyn tooling over time. (There is a distinction between the Roslyn compiler and the Roslyn tooling I am not qualified to explain.) The fact is that as a functional first language it does not require as much surrounding tooling as C#. Requirements for refactoring tooling, for instance, are far simpler.


> Requirements for refactoring tooling, for instance, are far simpler.

If that's the case, it's sad that Visual Studio's refactorings for F# are so meager.


Yet I don't miss any refactoring tools in VS for F#. It is a simpler language and has some nice design affordances that makes refactoring superfluous in some cases.

I have a small story around it. A couple of years back I had the nasty habit of enumerating sequences multiple times with LINQ. Resharper constantly complained about it. Programmed some F# for projecteuler and advent of code. My nasty habit of multiple enumeration went away even in C#, because F# design affordances made me rethink enumeration and sequences. So since then I haven't triggered multiple enumration in Resharper with my own code. It is always somebody elses.


This is another tragedy of the bifurcation of the F# and greater .NET community.

Most of the investment in refactoring has been for Roslyn analyzers, which are C#/VB specific. F# has a completely difference compiler framework.

I don't know if the F# compiler service or Roslyn came about first, but they are not compatible with each other.


I have a special place in my heart for F#. While I do a lot of Scala these days, professionally and open source, F# was my first introduction to functional programming that eventually got me into Scala.

F# and Scala are extremely similar languages: garbage collected, hybrid OO/FP guest languages hosted on a widely-used runtime. Even though the superficial syntax is very different, and the advanced language features they provide are pretty different, the core experience of modelling your data as "dumb" data structures and transforming collections of data using higher-order functions is almost identical.

Scala ended up winning out for me due to a broader ecosystem (Much more OSS Java than OSS C# out there), better tooling (Visual Studio's F# support was always disappointing...), and easier interop (F# <-> C# feels a lot more clunky than Scala <-> Java). But I can easily imagine an alternate universe where I'm happily writing F# all day, and almost nothing would be different from my current work writing Scala.


Oh my so many warts. In haskell, ocaml but also F#.

> Specifically, method constraints were added, introduced by a deliberately baroque syntax:

  let inline (+) (x: ^T) (y: ^U) : ^V = ((^T or ^U):
   (static member op_Addition : ^T * ^U -> ^V) (x, y))
> This definition says that any use of + is implemented via inlining a call to an appropriately-typed op_Addition method, defined on the type ˆT or ^U, i.e. the type of either the left-hand or right-hand argument. The ˆT notation for type variables indicates statically resolved type parameters (SRTP), i.e. type parameters which are resolved to a nominal type at compile-time.

Why ^ and not using the existing : for seperating types from values? :T * :U -> :V would have looked like types, not pascal ptrs.

Equally interesting are ocaml warts like let x2 = 1.0 +. 2.0 (+ not overloaded or defined for floats), or haskell's decision to torpedo the "class" keyword.

Or ocaml hijacking ::

let xs = 1 :: xs, with :: being cons, which was initially the lisp dot notation '(1 . xs)

In retrospect perl's syntax is sane compared to this.


You're not actually supposed to use statically resolved type parameters. It's deliberately strange and difficult, because mere mortals are not supposed to use it.

`::` is normal for cons in an ML, right? Of the ML family of languages, I'm only aware of Haskell that chooses differently (they use the single colon).


I agree, though Haskell is certainly the most advanced. ML languages tend to be... primitive, at least to the modern eye. The lack of function polymorphism in OCaml is such a huge bummer. Modular implicits have been proposed (in a paper from 2014), but it doesn't seem that they are being actively worked on. If I could get multicore and modular implicits, OCaml would be the ideal language.


Modular implicits are being worked on actively, it is just a hard feature to get right in a scalable and future-compatible way.


There are a couple of concepts in F# that I really like:

* actor-based approach to concurrency[1]. Very useful when you want to design your code lock-free. Treating instances as agents that read messages from a mailbox, frees the developer from using low-level threading primitives. First time I've used this approach in production with AKKA and Scala. In F# it is much cleaner because the language itself is built on a better platform.

* exhaustive pattern matching[2]. Writing in F# means using pattern matching a lot. Exhaustive matching gives you the confidence to refactor and maintain your code. The compiler will warn you when adding a new value in your type and not covering the execution path that uses it. It catches a lot of errors before you even push the changes to CI.

* obviously immutability, conciseness, currying, these have been mentioned so far by others.

One nitpick in F# are Exceptions [3]. They approached it similarly to how it is done in C#. You are allowed to define and throw an exception that will "jump" somewhere in the execution path. I prefer when a method returns Option-style types. This way, you know what to expect from a call, and pattern matches on the result without adding one more execution path in your code, which covers exceptions separately. It was added to support C# style error handling, though I very much prefer an error-code-based approach.

[1] https://fsharpforfunandprofit.com/posts/concurrency-actor-mo...

[2] https://fsharpforfunandprofit.com/posts/correctness-exhausti...

[3] https://fsharpforfunandprofit.com/posts/exceptions/


A draft was discussed last year: https://news.ycombinator.com/item?id=18874796




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: