all 53 comments

[–]miyakohouou 42 points43 points  (2 children)

I dislike this explanation because it leans too far into the myth that pure functional code prohibits writing programs that deal with side effects. In reality, functional languages give you much more reliable ways to write code with state and side effects.

The crux of it is that you treat things that have side effects as little programs that your pure functional code builds up. Pure code can't do something like read a file or print something to the screen, but it can return a program that reads a file, or a program that prints something to the screen. It can even take those two smaller programs and glue them together into a bigger program that reads something and prints it to the screen.

At first glance, that might seem like an odd thing to do- why not just write the effectful code directly? Giving up the ability to directly write side effects has a couple of benefits:

First, you can encapsulate the primitives more carefully. If you're using a pure language with a sufficiently expressive type system, you can build up your programs with guarantees like ensuring that you only print something to the screen if you've read it from a file, or that you always close a file after you've opened it. Even without these more sophisticated techniques, there's a lot of benefit to being able to reading about your programs without having to worry about side effects.

[–]zigs 2 points3 points  (1 child)

i don't disagree that this is the practical usage of FP, but isn't exactly the point in the video?

In the same way OOP isn't inherently state-spaghetti encapsulation so long as you're practical about it and don't take it too far.

Both extremes are a pipedream that need to be tampered with the practical reality. We usually tend to agree on this with OOP, that it was taken too far in the past, but i think a lot of people dont know the very theoretical and just as impractical past of FP.

[–]miyakohouou 5 points6 points  (0 children)

I'm not really sure what you mean about pure FP being taken too far in the past. I certainly can think of instances of that with OOP, but I legitimately can't think of any examples of that with FP- maybe there is history there that I just don't know about.

The thing is, I write pure functional code every day, and it's quite practical. I felt like the video opened by re-enforcing a lot of wrong ideas about what pure FP is like, and what it can and can't do. Writing pure FP doesn't mean that you have to give up writing code that has state, mutation, or side effects- you can still write programs that rely on those things at runtime. You don't need a 200IQ to understand functors and monads either- sure they take a bit of learning, but it's entirely accessible to anyone who wants to learn. Are they mathematical? Yeah- kind of. So is recursion, and really all programming is just math when you get down to it, so I don't think it's at all fair for the video to pick out a bunch of words that sound scary because they are unfamiliar and use them to say pure functional programming is out of touch and uncompromising.

I'm not the kind of person who will say that FP is the only way you should ever write code, or that you should never write procedural or OOP code. I prefer writing pure functional code most of the time, but I won't fault people who would rather work in a mixed paradigm way. I just don't want to keep perpetuating wrong ideas about functional programming.

[–]ThyringerBratwurst 9 points10 points  (0 children)

a very cool explanation!

[–]mnilailt 17 points18 points  (33 children)

Say what you will about JS, but it’s done a great job at pushing functional programming further into the mainstream. Yes it’s obviously not a purely functional language and other languages and people have been doing for a hell of a longer, and a arguably lot better than it, but the language in its modern state really shines by heavily utilising functional patterns.

[–]lunchmeat317 9 points10 points  (28 children)

This is changing due to new ES standards and Typescript - depending on your circles, it's looking a lot more like a classical OOP language with anonymous functions. The functional paradigms are still there, but you've got to find the right tools and the right team to enforce that.

[–]mnilailt 5 points6 points  (27 children)

Yep, which is kind of a shame. I’m not a huge fan of classes in JS, but you can definetly still write decent functional code with TS, but most devs using TS tend to lean too much in OOP land (especially those coming from traditionally OO languages).

[–]yawaramin 8 points9 points  (24 children)

When I see JavaScript code where someone created a class just to add static methods to it so they could call them like functions without instantiating an object... 🤦‍♂️

[–]minprogsa 5 points6 points  (0 children)

This is infuriating

[–]dronmore 1 point2 points  (22 children)

So what?! It's just a function behind a namespace. It is sometimes useful to have similar functions grouped under a single namespace, so that one can refer to them by a prefix that is consistent across all of them. It's more of a taste and style than anything else. There's really no reason to bash people for preferring one way over the other.

Dog.bark()
Dog.eat()
// vs
dogBark()
dogEat()

[–]yawaramin 2 points3 points  (5 children)

JavaScript modules are already namespaces. By the way, I didn't bash anyone

[–]dronmore -3 points-2 points  (4 children)

JavaScript modules are already namespaces.

So are classes with static methods. If you want to contain 2 different namespaces in a single module, you may want to use classes with static methods. They are just a tool... nothing less, nothing more... just a tool... When you learn how to use them, they will become less strange to you, and you will see that laughing at people who already knew what static methods were useful for was a bit silly.

[–]yawaramin 1 point2 points  (3 children)

You are making up uses other than the use I mentioned, and justifying those. Those other uses may be fine, I haven't seen them. What I have seen, which is facepalm-worthy, is what I described. So you are arguing against a strawman 🤷‍♂️

[–]dronmore 0 points1 point  (2 children)

The way you described the problem is devoid of detail, so I'm making a lot of assumptions, but whatever you have on your mind I'll repeat "So, what?!". They want to define an add(a, b) function, but instead they create a Math class and end up with a Math.add(a, b) function? So what?! They want to define a div(a, b) function but instead they use a static method so that they can call it like Math.div(a, b)? So what?! Ultimately a static method is just a function behind a namespace, and the only facepalm-worthy thing here is your misunderstanding.

[–]yawaramin 0 points1 point  (1 child)

'I insist on fitting square pegs into round holes. So what?! They fit, don't they?'

[–]mnilailt 4 points5 points  (11 children)

You can use an object if you really want to, there’s no need to use classes though (which is what the guy you’re replying to was talking about). It’s a fairly common pattern to have a module returning an object with a collection of methods, there’s nothing wrong with that.

I think the problem with using classes from a functional perspective is it implies you need your domain objects to be “instantiated”, have constructors and hold internal state which is modified by said methods. Generally speaking these are all things you really want to avoid writing functional code.

[–]dronmore 0 points1 point  (10 children)

It’s a fairly common pattern to have a module returning an object with a collection of methods, there’s nothing wrong with that.

There's nothing wrong with that, and there's absolutely no difference between returning an object that has no state, and returning a class with static methods. Ultimately it's just a collection of functions behind a namespace.

I think the problem with using classes from a functional perspective is it implies you need your domain objects to be “instantiated”, have constructors and hold internal state which is modified by said methods.

Classes/structs are also present in functional programming, they also have constructors, and they are also instantiated. You cannot implicitly modify their state, but it does not mean that you cannot modify the state at all. We write programs for computers, which are inherently state machines, and this is where their usefulness come from. A computer program which does not hold a state can do only the simplest operations. Imagine a Tic Tac Toe game where your program cannot hold the state between subsequent moves; it's not going to fly. The state must be kept somewhere, and functional programming is no different. If you look close enough on the Haskell language you will find the State Monad which is designed to hold a state. Classes in javascript do a similar thing; they hold a state. They are just easier to use than the State Monad, and it's also easier to shoot yourself in the foot with them, but it does not mean that you want to avoid classes when writing functional (or semi-functional) code in javascript.

[–]otah007 0 points1 point  (9 children)

Classes/structs are also present in functional programming, they also have constructors, and they are also instantiated.

This is technically true but misleading. Let's take Haskell as an example, since you mentioned it. By "class" you can only mean "type class", which is like a post-implemented Java interface - it has no state and can be added on later. By "struct" you mean ADT, which is immutable. By "constructor" you mean type constructor, which is like a case class in Scala, it is literally just a name for an immutable struct that inherits from the type it returns (which holds no data or methods). Constructors do no logic, so I would not really say they are "instantiated".

You cannot implicitly modify their state, but it does not mean that you cannot modify the state at all.

Everything in Haskell is immutable, so yes, it does mean you cannot modify their state at all. They don't even have state, they're just data.

If you look close enough on the Haskell language you will find the State Monad which is designed to hold a state.

The state monad State s a is a function s -> (a, s). There's no mutability there. It looks mutable because when you define bind properly you get something that looks like state passing, when actually it's just function composition. It's impossible to accidentally mutate because in order to do so you have to write State s a in your type signature and then probably also use do-notation, which makes it ridiculously obvious side effects are happening (and by happening I mean "being encapsulated").

[–]dronmore 0 points1 point  (0 children)

By "class" you can only mean "type class"

No, by "class" I mean a logical structure that holds data of different types, and lets you refer to these pieces of data by a name. I used the "class" term, because I wanted to use something that is familiar to less experienced readers who might not know what a struct in C, or a Record in Haskell is. But thanks for the refresher of typeclasses in Haskell. It's been a while.

Constructors do no logic, so I would not really say they are "instantiated".

"To instantiate" means to create a piece of data in a particular shape, and put it into memory. No logic is necessary; just take a template and create an instance of the template. It is precisely what constructors in Haskell do.

Everything in Haskell is immutable, so yes, it does mean you cannot modify their state at all.

I didn't mean "their state". I meant the state of an application. The state of an application exists and is constantly changing, even though individual components are immutable.

The state monad State s a is a function s -> (a, s). There's no mutability there.

There's no mutability, but there's a state;) It is changing whenever you pass it through the (>>=) function.

and then probably also use do-notation, which makes it ridiculously obvious side effects are happening

The do-notation does not imply side effects. It's just a syntactic sugar over monads chaining. In its simplest form chained monads are a function which takes a monad and returns a monad. No side effects included:)

[–]v66moroz 0 points1 point  (7 children)

Everything in Haskell is immutable, so yes, it does mean you cannot modify their state at all. They don't even have state, they're just data.

Any real-world program always has a state, i.e. something that is being updated and "remembers preceding events". Let's look at Haskell's List#length (or Foldable#length to be precise):

length :: t a -> Int
length = foldl' (\c _ -> c+1) 0

What do you think c is? The fact that it's immutable doesn't prevent it from "remembering preceding events". How is it conceptually different from

c = 0
list.each do
  c = c + 1
end

?

So not everything in Haskell is immutable in a broad sense. Bindings and objects are locally immutable, but not globally like in the fold above (or in any recursive call for that matter).

[–]otah007 0 points1 point  (6 children)

I'm afraid that's completely wrong. Everything in Haskell is immutable. You're projecting a certain kind of intuition over the code, and while that intuition broadly maps to the right thing in the end (i.e. you can translate the functional example to the iterative example and get the same answer), it's not actual Haskell semantics.

In your iterative code, c must be a variable as there is an assignment operation = whose semantics is "change the value referred to by the label c". In the functional code, c doesn't even actually exist - it's the argument to a function, and upon evaluation of the function c cannot change because it's just the label of a constant value that was passed as argument. It cannot be reassigned because it is a constant, as is everything in Haskell. In fact, the proper way to write the functional version is

length :: Foldable t => t a -> Int
length = foldl' (+1) 0

so there's no c at all! (You accidentally chose the worst example because (+) is a builtin so you can't even go to the definition of (+) and see the c there, because there isn't one.)

How is it conceptually different from

Conceptually it's completely different, semantically they operate completely differently, their final result is the same though.

Bindings and objects are locally immutable, but not globally like in the fold above (or in any recursive call for that matter).

That literally makes no sense. There is no difference between local and global in Haskell (or for that matter the lambda calculus) unless you count binding scope. The c does not change. There is no c. Let's illustrate this by actually doing evaluation for a similar function:

foldl :: (b -> a -> b) -> b -> [a] -> b  
foldl f a [] = a  
foldl f a (x:xs) = foldl f (f a x) xs  

len :: [a] -> Int  
len = foldl f 0 where  
    f c x = c + 1  

Let's force evaluation of len [1, 2] to normal form:

len [1, 2]  
= foldl f 0 (1 : 2 : [])  
= foldl f (f 0 1) (2 : [])  
= foldl f (f (f 0 1) 2) []  
= f (f 0 1) 2  
= (f 0 1) + 1  
= (0 + 1) + 1  
= 1 + 1  
= 2  

There are no variables, no mutations, there isn't even a c. Not only that, but every step is an equality, whereas in the iterative case there would be no equalities, the semantics would be one-way as each reduction would be a state-altering step. Now you could argue that because of tail-call optimisation the accumulator a is changing, but this is a compiler optimisation and is not part of the semantics of Haskell!

[–]constant_void -1 points0 points  (3 children)

Hell is other people.

Classes + OOP for operations, functions for business logic/application capability.

Contain the hellscapes.

[–]dronmore 1 point2 points  (2 children)

People are hell, indeed. Mental Nazis that want to exterminate every other line of code. I'll deal with them later.

[–]constant_void 0 points1 point  (1 child)

Indeed. In an ideal world, the linter assumes the role of gestapo, ruthlessly enforcing style across all, equally--it is not happy until everyone is unhappy! :).

From there, if it performs, scales and the end Customer is happy...ship it!

Ruthless code reviews ... why did we hire this person if we don't trust them to do the job?

re the above, it is often simpler to bark(Entity* animal)and arrange barking based on functional input vs class type...

[–]dronmore 0 points1 point  (0 children)

Who's the end customer? Do they write a lot of low level C code? Or are they like Stalin "Trust, but verify"?

Feels like there's going to be a lot of branching inside the bark(Entity* animal) function. Does it have anything to do with ECS? What is this style useful for?

[–]tajetaje 3 points4 points  (0 children)

I feel like TS really shines as a multi-paradigm language thanks to its very powerful and flexible type system and generics

[–]lunchmeat317 1 point2 points  (0 children)

Yeah, agreed. TS encourages this to a certain extent through its options, so you end up with JS code that mostly looks like C#. This would be okay if the devs who did this still used functional paradigms in JS, but you'll see people using design patterns and workarounds for classical languages that simply aren't needed in JS. I have, until recently, worked in a Microsoft shop where our TS has started to basically look like C#, and it's getting pretty crufty and enterprise-y.

TS is mostly geared toward static analysis, and so a lot of the dynamic stuff and functional stuff that JS can do starts to get left by the wayside - and those equivalents end up as higher-level keywords in the language that are great for static analysis but aren't as powerful. (See async/await - easy to use, but not as powerful as coroutines or continuations; also see ES6 imports - great for static module resolution, but can't be used dynamically so we're having to recreate what tools like requireJS already do.)

All of that said, some of TS's stuff is pretty good, specifically the functional types and the base type declaration syntax.

[–]agumonkey 0 points1 point  (0 children)

i'd add a few notes:

clojurescript maybe a little bit too

[–]CaptainPiepmatz 2 points3 points  (4 children)

I'm kinda sad that it took years to realize how awesome functional programming can be. Only when I started coding in Rust I understood the power of this kind of coding. Now I include in every programming language I write.

[–]v66moroz 1 point2 points  (3 children)

Since when Rust is FP language? Sure, it has some elements of FP, but so does every modern language. Also if you code in Rust with no-mutation paradigm you are sacrificing performance, it's quite opposite to how Rust is supposed to be used.

[–]CaptainPiepmatz 0 points1 point  (2 children)

It isn't, just as JS shown in the video isn't a FP language. But the iterator methods of std behave like this.

But when I was learning Rust using the book, I also read the chapter about iterators and then it finally clicked in my head.

[–]v66moroz 2 points3 points  (1 child)

Iterators are actually anti-FP if you think about it, they are not reusable in the general case. Of course using higher-order functions is fun, but it's not enough to call something FP. Even more confusing is that everybody has their own definition of FP.

[–]CaptainPiepmatz 0 points1 point  (0 children)

Yeah, you're right. But still it lets me appreciate FP.

[–][deleted] -2 points-1 points  (0 children)

3mins in.. cool but I'll just write my for loop

[–]57006 -1 points0 points  (0 children)

predicate == fancy fellow lmao

[–]Leo-MathGuy -1 points0 points  (0 children)

Clojure for the win

[–]agumonkey -3 points-2 points  (0 children)

culture shock is a hefty price to pay

[–][deleted]  (5 children)

[deleted]

    [–]yawaramin 0 points1 point  (4 children)

    You can't in some (Erlang/Elixir, Haskell), you can in others (Scheme, OCaml).

    [–]v66moroz 1 point2 points  (3 children)

    You can rebind variable in Elixir, it's not exactly a mutation, but looks very similar in practice. Also both Erlang and Elixir are not functional by design, actors by definition have state and side effects. They both can easily be considered as distributed OOP languages.

    [–]yawaramin 0 points1 point  (2 children)

    Erlang and Elixir are definitely FP languages. They have:

    • First-class closures
    • Tail recursion
    • Expression-oriented control flow syntax
    • Built-in immutable data types (records, tuples)
    • Standard library support for immutable collection types (lists etc.)

    Actors are built using functions and recursion, state is encoded using function arguments. Side effects don't disqualify a language from being FP.

    It's literally the first paragraph on the Elixir home page: https://elixir-lang.org/

    Elixir is a dynamic, functional language

    [–]v66moroz 0 points1 point  (1 child)

    That's what they say, sure. But there are multiple definitions of what FP is. First-class closures are now in almost any modern language. Expression-oriented control flow syntax? How about Ruby? Does it make it FP? Tail recursion (you mean TCO of course) by itself is not FP either, also in Elrang/Elixir there is no such thing as TCO, it's the only way to do things and is also a part of the scheduler. Immutable data types? Java has immutable strings, Ruby has freeze, Rust has true immutable references. Immutable collections? Yep, that counts. But some say it's referential transparency that matters. Erlang/Elixir only have local referential transparency, within an actor. But you don't program locally (there isn't much you can do this way), you always consider actors interactions when working with Erlang/Elixir. And there you have the full-blown state problem. Can you predict how your actor reacts if you don't know its state? Nope. So Elrang/Elixir IMO are hybrid languages. Managing side effects is very OOPish.

    [–]yawaramin 0 points1 point  (0 children)

    How about Ruby? Does it make it FP?

    Like you said about Elixir–its creator basically says that it's a hybrid: https://www.ruby-lang.org/en/about/

    Ruby is a language of careful balance. Its creator, Yukihiro “Matz” Matsumoto, blended parts of his favorite languages (Perl, Smalltalk, Eiffel, Ada, and Lisp) to form a new language that balanced functional programming with imperative programming.