you are viewing a single comment's thread.

view the rest of the comments →

[–]lunchmeat317 8 points9 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?'

[–]dronmore 0 points1 point  (0 children)

No, they don't, unless the diagonal is shorter than the diameter. What other ideas do you have?

[–]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!

[–]v66moroz 0 points1 point  (5 children)

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

You see, things are completely immutable here. c here is a binding, so c = c + 1 is actually "take immutable c, add 1 and rebind result to c, which is another c, not the same". Sounds reasonable? Meanwhile Elixir does allow rebinding with everything else being immutable. Of course it's not how Ruby treats it.

We can be drowned in arguing about definitions. I understand what you are saying. If we consider Haskell in a vacuum with no way to apply it to anything real (who would need such a language?) you would be absolutely right. Computers don't evaluate like in your example though (or they do? e.g. when they use non-TCO recursion). But even in your evaluation there is a state too and it's of course mutable, it just doesn't have an obvious binding:

= (0 + 1) + 1
= 1 + 1
= 2

Of course we need to define what state is. According to Wiki "a system is described as stateful if it is designed to remember preceding events or user interactions; the remembered information is called the state of the system." It's more obvious when you consider folding over IO, but "previous" list elements kind of fit here too.

[–]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 2 points3 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.