6 ms·
>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 willi
by 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...