DEV Community

Discussion on: Pitch me on the pros and cons of your preferred web app framework

Collapse
 
mrjjwright profile image
John Wright

Great features!

Can you elaborate on why the normal dev struggles with these 2?

  • difficult to mentally separate data and behavior
  • strong but unnecessary instinct to "protect" data at every step
Collapse
 
kspeakman profile image
Kasey Speakman • Edited

Elaborate, I shall!

In general, habits/instincts are muscle memory. Something done or thought often enough that your brain develops an "integrated circuit". So you can perform that task faster or have a strong instinct about new information, without resorting to slow, logical thinking. (See Thinking, Fast and Slow.) OO has a bit of ceremony (especially typed) and some common footguns. So years into a career many of these instincts are heavily reinforced. Becoming a barrier to learning other ways of doing things.

  • strong but unnecessary instinct to "protect" data at every step

OO principles lead us to divide and distribute mutable state across many objects. It doesn't take a new dev long to figure out that uncontrolled access to data is a footgun. Behaviors can be easily broken (and hard to trace) at runtime if data they depend on is changed by outside code. So out of necessity, we develop the instinct to protect data. To tightly control changes to object state through object behaviors. (Curiosity: is most OO code about data protection?)

This instinct is necessary because state is mutable. The thing about instinct is it doesn't have nuance unless the experiences that built it highlight that nuance. So when I first tried FP I could not figure out how to code in a way that felt safe or "right". It took a while and a lot of partially-OO F# code to make mutability a pre-condition to that data protection instinct.

  • difficult to mentally separate data and behavior

The previous point is a strong motivator for packaging data and behavior together. But the principles of OO also lead us there. Encapsulation and information hiding to start. Liskov (L in SOLID). And many OO "code smells":

  • data classes - Note: a common exception is a DTO (Data Transfer Object) for reading/writing wire formats.
  • switch statements - It is common to have data that can be one of several choices. Example: Payment can be Cash, Check with number, CreditCard with number and expiration, etc. A good way to represent that is with an abstract class and shallow inheritance data classes. To extract data, it is necessary to use a switch statement on object type. Considered bad practice in OO but fine in FP.
  • feature envy and inappropriate intimacy - Both discourage using another object's data.

Overall, the instinct developed is that data doesn't belong on its own. In fact, many OO languages like C# encode this in the language -- data can only be defined as part of a class or object *.

* C# only recently added records, which are meant to be data only. Both C# and F# records use classes under the covers. They also expose data not as Fields but as Properties -- syntactic sugar for getter/setter methods over private data. Probably this design is for best compatibility with existing .NET tools/libraries/runtime primitives. Point being it's still deeply ingrained. Struct has been available for a while, but in .NET it is also class-shaped (constructor, methods, properties, etc.), and you don't want to use it as the default data container due to its runtime characteristics (stack allocated).

Thread Thread
 
mrjjwright profile image
John Wright

Thank you so much for the full response! This data over protection angle is an interesting one that I had not heard in quite the way you are putting it.

Thread Thread
 
kspeakman profile image
Kasey Speakman • Edited

You are welcome.

I've also seen people get really hung up by validation when switching from OO. Because we're accustomed to using the same mechanisms to "protect invariants" as we do to defend mutable data. We can intercept every new object (in constructor) and data change (in methods) to protect those invariants. Coming to FP and records, consumers set the data directly. Specifically in F#, record constructors and the built-in update syntax are not interceptable. So when someone suggests "just make a validation function", this feels like it can't possibly be sufficient by comparison.

But it is. And it is a cleaner separation of concerns. Sometimes consumers want the ability to use invalid data. Like representing what the user typed, even when its not yet fit for submission. I can use the validation function to make sure invariants hold. Without having to care how it was constructed or changed before it got to me. The consumer can also use this function to know if it's Ok to send.

Thread Thread
 
mrjjwright profile image
John Wright

I thought this might be underlying what you were saying, that makes great sense.