14 ms·
F# is 3 big things to me: Safer threading with immutability Safer programming with null-safety Safer logic with precise domain modeling The precise domain m
by algorithmsRcool 8y ago
F# is 3 big things to me:
Safer threading with immutability
Safer programming with null-safety
Safer logic with precise domain modeling
The precise domain modeling is the real paradigm shift.
The whole point of static typing is to inform the compiler about your intent so that it can provide guarantees about correctness. F# makes it easy to define lots of small types that precisely model state so that you can give more responsibility to the compiler to verify your program.
I cannot overstate how important this is to maintaining correct software overtime. If you guard your touches with non F# code, you need far fewer unit tests and probably even less runtime checks because you already know if the types are right, so is the logic. And when you need to write unit tests, the functional style makes your tests very easy to write.
I have personally struggled with domain complexity in C# that i was able to model precisely in F# and have it work perfectly on the first try.
- contravariant 8y ago>I have personally struggled with domain complexity in C# that i was able to model precisely in F# and have it work perfectly on the first try. If you're willing to provide a (simplified) example I would be very interested.
- ambulancechaser 8y agoWe found it quite nice for properties of objects that appear over time. Think things like Order that might or might not have delivery details. In C# you are making classes with nullable delivery timestamps, delivery person, etc. And one or two properties isn't that bad but it gets a little onerous when you start to have constraints like "these four properties are either all null or all populated". In F# it is trivial to set up a new constructor for Delivery that includes all of these properties. There is no unspoken agreement about that, you can set it in the model.
- algorithmsRcool 8y ago> There is no unspoken agreement... Yes! That is exactly right, F# give you the tools to express your actual model with little/no ambiguity.
- contravariant 8y agoI see, although it looks like you're really after a state-machine there. Which I admit aren't the easiest to create in C#, and immutability and null-safety definitely make it easier... Although personally I'd rather have the different states encapsulated in separate classes than a single type that encapsulates all possible states and enforces them through the constructor.
- victorNicollet 8y agoThis is what F# does (define multiple classes), but each of these classes only takes one line of code.
- sparkie 8y agoAlso, it defines them as nested classes, and marks the outer class as `sealed`, as they're closed types, and it prevents the type being extended with new cases. C# can't do this because the compiler rejects putting `abstract` and `sealed` on the same type.
- contravariant 8y agoYou can kind of solve this by making the constructor of the outer class private and defining all subtypes as nested types. It's not ideal, but it works.
- mavelikara 8y agoWhy do you struggle with this in C#, especially given that you are familiar with the F# style? I don't know C#, but in Java I'd write this as: public class Order { final Optional<DeliveryDetails> delivery; public Order(Optional<DeliveryDetails> delivery) { this.delivery = delivery; } } public class DeliveryDetails { final long deliveryTimeMs; ... public DeliveryDetails (long deliveryTimeMs,...) { this.deliveryTimeMs = deliveryTimeMs; ... } } My IDE writes most of these lines for me. I believe C# will have similar or more succinct constrcuts.
- ambulancechaser 8y agothat's true, you can recreate it in some sense but you can't get to what F# can guarantee. A big promise of discriminated unions is the ability to make invalid state unrepresentable. Matching on the different type constructors is a fantastic way to only express coherent states.
- uryga 8y ago> Matching on the different type constructors Terminology nitpick: Pattern matching is done on value constructors (or just "constructors", but at the intersection of FP and OOP that could be confusing). "type constructor" means something like `List` (as opposed to `List[Int]`) – a generic type that hasn't been applied to any type argument(s) yet, and will "construct" a type (like `List[int]`) when you apply it.
- tigershark 8y agoThe ide maybe will write a small part of it, but you’ll have to keep reading it forever. And obviously it is modelled wrong because it is possible to have a Delivered order without delivery details, there is nothing that enforces it. Compare it with how I would write it in f#: type OrderId = OrderId of string type DeliveryTime = DeliveryTime of long type DeliveryDetails = { deliveryTime: DeliveryTime...} type Order = { id: OrderId ...} type DeliveredOrder = { id: OrderId, deliveryDetails: DeliveryDetails...} Probably it is even better to define delivery time using the unit of measures and specifying it as ms. What is the difference in this way? That an order cannot ever have DeliveryDetails, while a DeliveredOrder must have DeliveryDetails. As a bonus you can’t just pass any string as an order id (for example a description) but you need to pass an actual order id. the same is true for the deliveryTime with the adddd advantage that you won’t be able to perform operations on it with a different unit of measure. In java or c# you would kill yourself if you try to do something similar and moreover you won’t have all the constraints specified here and the immutability automatically enforced.
- algorithmsRcool 8y agoI don't think i can release the exact code but my case was like this. I was writing a little program to help glue some things together in our build/release pipeline. This tool would be deployed to the build server and get invoked by the build agent. (This could have been a script, but the complexity got to be too much to keep organized) The tool had two halfs: - The frontend whose job was to gather up all the 'input' from CommandLine and Env vars, do some parsing, then spit out proper types/objects. - The backend that would interpret this data and make decisions, make some API calls and maybe copy some files. Because of the way our our software is built, we have 5 or 6 different 'flavors' of our app that needed special treatment during build. The complexity of branching on if it was a build step or a release step, the different flavors of our app and the need to deal with input data that may or may not be there got the best of me and I spent weeks making tweaks to deal with NREs at runtime because i hadn't handled some weird case. So i trashed the tool and rebuild the front end in F#. I spent a little time making a very accurate type representation of the data model. Including defining a lot of stuff as optional and introducing a lot of discriminated unions to represent possible branches. Then i essentially just filled in the blanks (match cases) and fixed the compile errors until every case was covered and I was done. No bugs. You see the big, big win of F# is the default path doesn't let your cheat yourself. You MUST handle every switch/match case. You MUST fully construct your records. If your function may fail, you MUST use Option to express None/Null And you MUST handle every option type as potentially None and write handling logic When you define your data model, just be on honest about what data needs to be where and the compiler will keep you on the straight and narrow.
- contravariant 8y agoI really need to make time to look into F# some time, it sounds like it has the things I like in C# but more so.
- yawaramin 8y agoYou can try it out quickly and cheaply, at https://fable.io/repl/ https://fable.io/repl/ There's no setup required, just start typing some F# code into the online playground and see it run immediately.
- rbonvall 8y agoThere are some nice examples here: https://fsharpforfunandprofit.com/series/why-use-fsharp.html https://fsharpforfunandprofit.com/series/why-use-fsharp.html I like this one: https://fsharpforfunandprofit.com/posts/designing-for-correctness/ https://fsharpforfunandprofit.com/posts/designing-for-correc...
- tokyodude 8y agoThere's a good example with explanation here https://fsharpforfunandprofit.com/posts/designing-for-correctness/ https://fsharpforfunandprofit.com/posts/designing-for-correc...
- pimeys 8y agoI would say it's the same with Rust and Haskell. And I agree, the superior type system of these languages is really a game changer for me and it's very hard to go back to languages missing these features.
- sifoobar 8y agoFor a while, yes. I know from experience that going the other way is a struggle as well. Habits die hard and change takes effort. There's nothing superior about these type systems, they're simply two of the most static and rigid systems in circulation right now. Try writing C, Lisp and Forth until it clicks. Then we can sit down and have an informed discussion about missing features.
- elcomet 8y agoThey are not just static and rigid, Haskell has type inference , which is what ihmo makes it superior
- bunderbunder 8y agoI have spent some time loving all 3 of those languages, and also, most recently, F#. I really do think that an ML-style type system is the better way to do domain modeling. The compiler support you get in F#, such as ensuring that your match expressions are complete, are nice. But what really makes me enjoy working that way is that (the non-OO bits of) F#'s type system makes it very easy to create domain models whose intent and inner workings are just obvious. You're right, it's not correct to cast the differences as "missing features" - it's just different ways of doing things. And each has its own advantages and disadvantages. F#'s set of tradeoffs is just the one that most suits my tastes when I'm building LOB applications.
- weavie 8y agoI think a lot of it could be personality based. With C, Lisp and Forth you bash out code and run it, see if it works and then make changes. You get much faster visual feedback. With Haskell, you need to put in a lot more up front thought. Then stuff doesn't compile and you spend ages working out why.. You can go for hours without having anything running. The cause of the errors can be quite abstract and obtuse and may seem like it is nothing to do with the actual problem you are trying to solve. Different people just suit different styles.
- sparkie 8y agoOne of the features that makes F# great for organization is the sequential ordering of compilation units which makes it easier to understand code dependencies, and makes it awkward to create mutually recursive types (requiring them to be in the same code file separated by `and`, or defined in signature files in advanced). This might sound like an unwanted restriction, but 9 times out of 10, having mutually recursive types is signs of a code smell. You can almost always model your problem better with a slight indirection through an interface or function, it helps avoid the need for mutation, and makes testing simpler.
- algorithmsRcool 8y agoI mostly agree about mutually recursive types, but I really hate having to keep my files ordered in the project honestly. It might be my least favorite thing about F# (Except that Option<T> is a class not a struct)
- piaste 8y ago> Except that Option<T> is a class not a struct There is ValueOption<T> now. It's a mouthful and you'll still have to deal with all the standard library, or other third-party libraries, which produces and consumes Option<T>, but you can address that with some aliases and conversion operators if you want to go all-in: type 't voption = ValueOption<'t> type VOption<'t> = ValueOption<'t> let inline (!) opt = if System.Object.ReferenceEquals(opt, null) then ValueNone else match opt with None -> ValueNone | Some x -> ValueSome x let inline (?) vopt = match vopt with ValueNone -> None | ValueSome x -> Some x;; // usage let x = !(List.tryFind (fun x -> x = 0) [1]);; // [<Struct>] val x : int voption = ValueNone
- konamicode 8y agoThere is Mechanic (https://github.com/fsprojects/Mechanic https://github.com/fsprojects/Mechanic), an OSS project that's meant to take away some of the pain, though I don't have any experience using it and I'm not aware of how useful it is in practice.
- clausok 8y ago
- tatoalo 8y agoI’ve also really liked FsCheck, if well configured of course. Just a shame that the university exam I took this summer was to implement binary trees and a parser...