• she/her

Principal engineer at Mercury. I've authored the Dhall configuration language, the Haskell for all blog, and countless packages and keynote presentations.

I'm a midwife to the hidden beauty in everything.

💖 @wiredaemon


discord
Gabriella439
discord server
discord.gg/XS5ZDZ8nnp
location
bay area
private page
cohost.org/newmoon

posts from @fullmoon tagged #fp

also:

prophet
@prophet

people will literally complain that OOP forces everything to be an object and then gush about how their favorite language has first class functions


GardenerAether
@GardenerAether

there are many legitimate criticisms you could make against modern OOP; the problem is people tend to conflate those problems with objects themselves

for example, inheritance is basically indefensible, and maybe even sub-typing generally. classes are also a complete bullshit hack that feel like it. basically every language ive ever used with these (and other features relating to them) has resulted in a pretty shitty overall experience

the other problem is that OOP programmers kind of evangelize objects for no good reason, as if encapsulation will fix every problem in every codebase immediately which. is just false. encapsulation addresses a very specific issue in state management. so when certain programmers ramble about how perfect their codebase and praise the magic of objects for it, everybody who isnt recognize the many problems with state management that still exist within the code, but then misinterpret it as a result of objects themselves, instead of an over-reliance on them which is what it. and in the end neither party really learns anything and we continue making the same programming languages rather than trying to break from those paradigms to make something more useful

if you strip all of that away, there is good evidence to support encapsulation as a viable strategy of state management for certain problems

and i think thats a legitimate criticism against both OOP programmers and functional programmers; very often they dont understand that both of their solutions are good when applied in the right context, and actually work much better in tandem with each-other than on their own

or in other words, the main problem with OOP (and FP) is dogma

when working on puppy's type system, i tried to follow a few guidelines:

  1. use pure functions to limit the mutability of shared state
  2. use linear functions to limit the sharing of mutable state
  3. use total functions to guarantee the production of state
  4. use dependent types to limit the possible configurations of state
  5. use encapsulation (objects) or delegation (effects) to explicate the way in which state is exposed

no individual one of these things is able to solve all of state management (and frankly theres probably some other problems and solutions that i just havent noticed yet). that doesnt mean that theyll make your code base suck, but an over-reliance on any one of these in isolation sure as hell will. i think its extremely important to use simultaneous combinations of these approaches if you actually want to solve the problem of State Management

fuck inheritance though. i dont know what people felt like they were thinking but they clearly werent. wtf


fullmoon
@fullmoon

I'm going to explain why object-oriented programming specifically is bad even if you get rid of the other problems typically associated/conflated with object-oriented programming (including inheritance). It's actually objects and object-orientation that are part of the problem.

The problem with object-oriented programming is that objects promote weakly-typed abstractions/interfaces because they encouraged reusing the same type of object (a.k.a. "class") over and over again, even if it's no longer the most appropriate type for the task at hand.

In other words, you can think of a chain of transformations on some objects as a sequence of method calls whose main input and main output are some object type (which I will denote O in the following diagram):

O(method)O(method)O(method)O
syntax highlighting by codehost

In other words, each method call in the above chain of data transformations takes some object (of type O), it might take some method arguments (which I've omitted from the above diagram), and it will produce a new (typically transformed) object of type O. We chain together a bunch of methods, which gives us our final object (also of type O).

Object-oriented programming encourages and promotes reusing the same object type repeatedly in a chain of data transformations. That's what makes it object-oriented programming.

Contrast with functional programming, where it is more common and idiomatic for each step in a chain of data transformations to be a different type (which I've denoted with A, B, C, and D in the following diagram);

A(function)B(function)C(function)D

Now, you could argue that you can do this in object-oriented programming, too. You can have object methods which return some new type of value (that is not the original object), and this isn't even uncommon, but it's not object-oriented programming. The thing that makes object-oriented programming idiomatically object-oriented is reusing the same object type/value over and over again.

However, reusing the same type ("class") of value ("object") promotes weakly-typed programming idioms. Usually, the way you do things in a strongly-typed way is that at each step of your data transformation you choose a suitable type that is the most precise type for each stage of your data transformation. This lets you make invalid states unrepresentable. However, if you insist on using the same value for every stage of the transformation then you're going to end up with weakly-typed idioms.

A classic example of this weakly-typed idiom in the object-oriented world is the builder pattern, where you'll start with a record with a bunch of nullable fields, like this one:

data Person = Person{ firstName :: Maybe Text, lastName :: Maybe Text }

newPerson :: Person
newPerson = Person{ firstName = Nothing, lastName = Nothing }

… and methods to set each field to a non-null value:

setFirstName :: Text -> PersonPerson
setFirstName first person = person{ firstName = Just first }

setLastName :: Text -> Person -> Person
setLastName first person = person{ lastName = Just first }

… and then finally you define a function to get rid of the nullability to build the final result:

data FinalPerson = FinalPerson{ firstName :: Text, lastName :: Text }

build :: Person -> Maybe FinalPerson
build Person{ firstName = Just first, lastName = Just last } =
    Just FinalPerson{ firstName = first, lastName = last }
build _ = Nothing

-- example₀ = Just FinalPerson{ firstName = "hubert", lastName = "hodges" }
example:: Maybe FinalPerson
example= newPerson & setFirstName "hubert" & setLastName "hodges" & build

… but this isn't great. Specifically, it's weakly typed because now we have to handle nullability. What if we forgot to set the first name or last name? Our code fails (needlessly):

-- example₁ = Nothing
example:: Maybe FinalPerson
example= newPerson & setFirstName "hubert" & build

The equivalent functional solution would be … to just not do that. You'd throw away the whole builder design pattern (which is kinda worthless IMO) and just create the desired final record in "one shot", like this:

example:: FinalPerson
example= FinalPerson{ firstName = "hubert", lastName = "hodges" }

… and now there's no room for error/failure.

This "tighter" way of doing things is more common in functional programming because there is no obsession with reusing the same type ("object") over and over again. Defining new objects in object-oriented programming is very heavyweight, so people try to get as much use out of them as possible by repeatedly using the same object repeatedly. In contrast, functional programming emphasizes lightweight type definitions, so they're cheap to define and you can have a lot of them and you're not "married" to any one particular type.

You can do this sort of thing in an object-oriented programming language, too, but the more you do so the less your programming style becomes object-oriented. Object-oriented programming is (in my opinion) only strongly typed to the extent to which you ignore the object-oriented idioms.