DEV Community

Cover image for SQL makes me uncomfortable.
Keshav Singla
Keshav Singla

Posted on

SQL makes me uncomfortable.

In my working (not theoretical) understanding, object-oriented programming is not merely an alternative to the traditional functional paradigm but often feels like a step ahead. I think of it as functional programming with state management. In practice, classes behave like functions with some inherent memory that lasts for the runtime of the program, making it possible to build better stateful abstractions that model real-world logic and concepts.

I recently tried my hand at React and its functional components. I instinctively thought of a webpage as a tree—parent components built from smaller ones, with the root being the final component to be displayed. The concept of reusable components thus becomes analogous to abstraction, similar to interfaces or abstract classes as seen in object-oriented programming. But for these components to be meaningful, they must hold data, and the UI must update when the data changes. At this point, the components that hold and react to data feel analogous to the “stateful abstraction” interpretation of classes.

This is why it made a lot of sense to me to think of UI as an “Object Tree.” Most traditional mobile UI frameworks reinforce this idea: Android with Java or Kotlin, and iOS with Swift or Objective-C, are all object-oriented languages. These frameworks model UI as hierarchies of objects as well. Why, then, did React abandon this model in favor of functional components, delegating state management almost entirely to the framework and giving developers far less explicit control?

The answer, I have found, is not really about functions vs. objects. It is about imperative vs. declarative ways of programming. Declarative UI describes what the interface should look like for a given state, letting the framework handle the how, as in React. Even modern mobile UI frameworks like SwiftUI and Jetpack Compose mirror this idea.

Traditional UI Kit (Imperative)

class CounterViewController: UIViewController {

    var count = 0

    let label = UILabel()
    let button = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()

        label.text = "Count: 0"
        button.setTitle("Increment", for: .normal)

        button.addTarget(self, action: #selector(increment), for: .touchUpInside)
    }

    @objc func increment() {
        count += 1
        label.text = "Count: \(count)"
    }
}
Enter fullscreen mode Exit fullscreen mode

Modern SwiftUI (Declarative)

struct CounterView: View {

    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

React (Declarative)

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Notice the similarities between SwiftUI and React:

SwiftUI React
@State var count useState(count)
count += 1 setCount(count + 1)
body return (...)
View function component
Recompute body Re-render function

I have to agree that this makes the code simpler, more readable, and reactive. Imperative UI dictates how to build and modify the UI step by step (e.g., old Android/iOS, UIKit, etc.), giving developers direct control but leading to more boilerplate, complex state management, and manual updates as apps grow. Apparently, new frameworks like Compose and SwiftUI also use a similar idea: define the output, define the inputs, and specify how changes in input state should change the output.

But there is a certain discomfort in this paradigm of programming, and I have felt it before. This is the same reason I don’t like SQL and why I prefer PyTorch over TensorFlow. It is the explicit control vs. ease-of-implementation tradeoff. These abstractions make code less verbose and more elegant—TensorFlow’s model.fit() is a perfect example—but they also make it much harder to answer basic questions like: When exactly does this run? or How many times? It makes it almost impossible to find bugs—bugs now depend on state transitions, not lines. The power is real, but so is the risk of shooting yourself in the foot.

Declarative programming asks us to stop thinking like engineers issuing commands and start thinking like designers describing constraints.

As a beginner in software, I don’t claim that functional components or declarative programming are inherently bad. They clearly work and are probably better suited for building systems at scale. For many developers, that is a price worth paying. For me, however, it remains an uncomfortable tradeoff. I can work within declarative systems, and I understand their value, but I still feel the loss of explicit control.

Top comments (0)