Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Classes are a way of writing higher order functions (stopa.io)
124 points by stopachka on Sept 9, 2020 | hide | past | favorite | 146 comments


> Yup, really. Classes are just higher order functions, which accept arguments (constructor) and return a list of functions that can manipulate those arguments (methods).

Nope, really they are not. Rather, objects are run-time, publicly accessible environments (bindings of names to slots).

Since functions also have environments, we can come up with some contrived use cases whereby a rigorously encapsulated OOP example revolving around a class that is sealed against extension and uses no inheritance or other OOP mechanisms is nicely mimiced by a vector of functions that share the same environment.

But imitating some facets of a thing is not being that thing. A dog standing on hind legs isn't a man.

A more fully featured OOP can be built up, which yet keeps using lexical variables for slots. Public access to slots by name can be implemented by having a standard function supported at the same position in the function table, which takes a symbol argument, and switches on it to map it to a variable in its environment.

Providing possible implementation mechanisms for thing is also not being that thing.

We can easily reverse this, too, and claim that functions are just objects. Compilers for lexical closures do things like flatten the environment into an array in which items are accessed by position. So, first class functions are, under the hood, nothing but code, plus an array, in machine language. And since data structures are also some code offsetting into arrays, it's all the same.

You know, the moment I saw a NAND gate schematic, my mind opened to an amazing revelation: we don't need both zeros and ones, just zeros and NAND gates! The number one is nothing but a zero, duplicated into the two inputs of a NAND gate.


> You know, the moment I saw a NAND gate schematic, my mind opened to an amazing revelation: we don't need both zeros and ones, just zeros and NAND gates! The number one is nothing but a zero, duplicated into the two inputs of a NAND gate.

This is not as absurd as you think it is. The most commonly used definition of the natural numbers (https://en.wikipedia.org/wiki/Peano_axioms#Formulation) is structured in a way similar to your description: we have a single explicitly declared natural number (zero), and all other natural numbers are defined as a sequence of function applications on zero (ie 4 is defined as S(S(S(S(zero))))).

Our notation for numbers is just "syntactic sugar" on top of this representation: useful when doing practical work with numbers, but only a hindrance when you're studying the concept of "number" itself.

Analogously, in programming language theory (studying the properties of programming languages moreso than trying to do program things in them), we often prove properties of languages by studying extremely frugal, tiny languages (often called "calculi"), that only have the absolute minimum primitive constructs.

Realizing that a construct can be expressed as "syntactic sugar" on top of a simpler one (objects on top of closures, for instance), is useful in this setting because it reduces the number of cases in proofs.


This touches on an interesting topic I've been thinking about for quite some time now. I have a somewhat reverse feeling about the definition of natural numbers (or for that matter, about the 'foundation of mathematics' as in ZFC set theory or whatever you want to choose) than you've presented above.

Those definitions were developed after we had something useful in order to 'codify' it and make it more precise. They are not, in my opinion, a basic blocks that the rest is built from but an added layer of abstraction on top of whathever it is you're defining.

I don't mean I don't understand set theory and the role it has, or advocating for any other system, I absolutely love it.

What I mean is, in practical things (programming languages etc.), it feels a bit counter-productive to me to reduce them to the simplest possible definitions/terms (that have to fit to the particular use case by definition), declaring that a building block and then explaining every other concept in those terms as if it was a proof that we have trully found a foundation and the rest is just a 'syntactic sugar'

edit: You all probably know this famous gif of a ballerina that can rotate to the left or to the right depending on how you think about it. After writing the above post the notion of S(S(S(S(0)))) keeps flipping in my brain between being an abstraction of and simplification of natural numbers. It's weird.


I agree with what you're saying, actually. I think these questions require drilling down to philosophical questions such as "what are numbers", "what is the essence of 'number'", "are numbers even real", etc. So I'll give it a go and be shamelessly long-winded about it :)

I think that numbers, set theory, programming languages, etc, are all abstractions. Forming abstractions is a mental tool associated with our ability for language: we spot a pattern and we give that pattern a name. Once we've given it a name, we can talk _about_ the abstract concept as a subject of our statements.

Perhaps the simplest form of abstraction we're capable of is "categorization". Ie we see a pointy-eared small animal that makes meowing sounds, we another one, and another one, ... and even though none of them are identical, our brain picks up on certain patterns in these creatures. We know that when we next see one of them, we can expect certain behaviors. So we give it a name, let's say, "cat", and now we can talk about the abstract concept of "cat" without referring to any specific individual cat. To see how big a deal this is, I find it useful to remember that cats aren't explicitly defined in the fabric of the universe, like they would be in a computer program (where we'd have a "Cat" data structure or object, usually), they're just emergent behavior of interacting molecules.

But we can do more, we can also abstract out properties of objects. We can see that some frogs have a similar color to the leaves of most trees, so we can give this leaf-color a name, "green", and talk about the abstract concept of "green" without referring to a specific green thing. You can just say "I like green" (no one will ask you "a green what?"), or "mixing green and blue makes cyan" (no one will ask "mixing green and blue what makes cyan what?").

And then we also have "chunking", where we can talk about sets of objects as objects themselves. So we can talk about "a herd of sheep" as a singular thing. Our ability to abstract out properties of objects also applies to these chunked concepts. One property of a set of objects is "quantity". Speculation ahead. Initially we probably only had fairly vague terms to talk about quantity, perhaps individual words for small quantities (one, two and three, maybe), and after that it's just "many". But it seems we figured out pretty fast that counting sheep, counting apples, or counting pebbles are really in some sense "the same thing", so we might have first learned to count through some unary "notation" even though we had no names for quantities. We'd make carvings into bone or carry a bag of pebbles, one carving or one pebble for each sheep in our herd. Then we notice that certain operations on these quantities are also all "the same": it doesn't matter whether you're adding pebbles, sheep, carvings, ... you're always doing the same thing. So after a while we start using these operations to define bigger numbers (the French still call the number 80 "quatre-vingts" or "four twenties"). Now all numbers become nameable, and eventually we streamline and standardize these names.

So now we have a name for every number, and we can talk about "thirty-two" without getting back a confused reply "thirty-two what?". And we can talk about operations on these numbers, like addition, multiplication, etc, but our hunger for abstraction isn't satiated at all. Since now we can talk about operations, we can also talk about properties of operations, so we notice that you can flip the arguments of additions and multiplications, and we give that property a name, commutativity, and we go on to invent abstract algebra.

After doing maths for a few thousand years we start looking into patterns into the different, disparate branches of maths at the time (geometry, number theory, logic, etc), and notice that the common pattern is that all of them are doing deductive reasoning, and that should be modellable by logic. So we abstract out the common bits and come up with set theory and proof theory and the like. And in that process we notice that number theory has unnecessarily many axioms and Peano figures out we can just use induction to define the set of natural numbers.

So yes, our intuition for numbers precedes Peano's definition by millennia or far more than millennia. But I'd say that's how it goes for _every_ definition. We always start by noticing some pattern, which initially we can't quite put our finger on (for example, Leibniz had a vague intuition that a lot of work in mathematics was in some sense so mechanical that one should be able to have a machine do it; he couldn't quite define computation yet but his hunch was spot-on), and then we try to refine our thoughts and come up with a definition.

In that sense, I don't think it's more meaningful to worry about the "true nature" of a number (the Peano definition, or some other notation?) than it is to worry about the "true nature" of the color red (is it its wavelength on the electromagnetic spectrum? but a wavelength can be expressed as a number; so is the essence of red just a number? or its RGB value, which is linked to how humans perceive color?). Both numbers and colors are abstractions, and abstractions are mental tools. They are a means to an end: to make sense of the world and communicate about it to other humans. And we use just use the tool that is most helpful given the problem at hand. For most practical applications we're going to use a notation that's compact and easily manipulated (Arabic numerals), but when we're studying the foundations of mathematics, it's more sensible to reduce everything to its smallest, most elegant definition possible. Neither view is "more true" than the other, in both cases you're doing no more than naming patterns in herds of sheep and piles of apples.


This is a fantastic explanation ookdatnog. Thank you -- if you have a blog would post this up! More people should see this :)


As a compliment for those following the Wikipedia rabbit hole, the repeated function application To represent the natural numbers is referred to as https://en.m.wikipedia.org/wiki/Church_encoding#Church_numer.... One of those calculi is lambda calculus (often extended with some syntactic niceties in PL research)


Fantastic explanation ookdatnog, thank you!


[flagged]


But it doesn’t matter what you use to bootstrap something because we talk about abstractions. And it is absolutely true that NAND an F form a functionally complete set of gates.

> But great mansplanation, carry on.

What a weird way to be overtly sexist when you don’t even know the genders of the people involved.


You can define logic circuits abstractly, without referring to their implementation in electronics at all.

Even if you insist on defining them in terms of electronics, I don't think there's a significant difference between assigning some meaning to an arbitrary voltage vs assigning some meaning to an arbitrary squiggle on a piece of paper.


One way to think about object instances is that they are namespaces full of partially applied functions, the applied argument being `this` or `self`; Python makes it excessively obvious.

The function that produces these namespaces is usually the `new` operator, its argument, the "constructor", just allows to set up the common partially applied value (and run arbitrary code along the way).

This partial application is not a serious problem; the mutability and the inheritance are.


Objects are just a poor mans closure.

Closures are just a poor mans object.

(Not sure where this quote is from originally? Maybe here: http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/m...)


There also is the classical example of defining cons, car, and cdr as closures in SICP:

  (define (cons x y)
    (lambda (m) (m x y)))

  (define (car z)
    (z (lambda (p q) p)))

  (define (cdr z)
    (z (lambda (p q) q)))
Here, cons, instead of returning an object with two values, returns a closure that captures the two values. car and cdr call that closure, passing it another closure. The cons closure calls that closure, passing the the two values.

Explanation at https://stackoverflow.com/questions/21769348/use-of-lambda-f....




Also, https://en.wikipedia.org/wiki/Scott_encoding

Of course, it completely kills (or "stress-tests", if you will) the branch predictor in your CPU.


It means that the "rich" man has objects and closures, for the situations where each one is the best fit.


See https://www.perlmonks.org/?node_id=75831 for a similar sentiment from me a couple of years before that.

My exact quote was, While closures may look like a poor man's object, I think the reverse is closer to the case.


That's literally true in Lua, where there is no such thing as object-orientation but there's lots of ways to do object-oriented programming. The simplest, most performant way I've found is to return a hash map containing lexical closures and data from a "constructor" function. Any enclosed functions which refer to values declared in the constructor use those values in a closure, so it's easy to do instance or static member fields by putting the field declaration inside or outside of the constructor function.

You can do it with metatable manipulation as well(changing the functions which map to specific table operators which lets you change how unmapped keys are handled) but I found the lexical closures to make a lot more sense.


> Maybe here: http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/m....

As far as I know, yes, that's the oldest I've been able to find.


It'd be nice to be able to blur the lines. Maybe implement multireturn by declaring variables public inside your closure scope.

  function Person(firstName, lastName) {
    let fName = firstName; 
    let lName = lastName;

    public getFullName = function() { 
      return fName + ' ' + lName;
    };
    
    public setFirstName = function(firstName) { 
      fName = firstName;
    };
    
    return this;
  }
("return this" is probably not the best syntax at the end there, especially if bolting this onto javascript.)


I remember trying to think about something similar when I wanted to invent a language that tries to stay as close to “everything is an X” as possible. (But it was a mess, unfortunately)

I would use “return scope”.


"scope" was actually on my short list of possibilities :-)


That's just Python classes.


Probably the early schemes for doing objects in LISP, from the Symbolics refrigerator-sized LISP machine era, which worked that way.


Closures in which sense here? Like closure operators?


Closure in the sense of a function which capture free variables in the outer scope. The captured variables are like fields in an object instance since they retain their state between invocations of the function. So a closure can be thought of like an object with a single method - hence a poor mans object :-)


This correspondence is made abundantly clear by C++ which explicitly defines its lambda functions ”as if” they desugared into objects of appropriate anonymous compiler-generated struct/class types. Also Java, which has had closures since day one, but used to call them ”anonymous inner classes” and forced people to use a ridiculously verbose syntax to define them.


This is literally how Delphi implements closures/anonymous methods.

The compiler will generate a class with fields for whatever variables are captured, and then instantiate it for you, assign the fields etc.

If you define your function reference as a type (as it's called), you can even inherit[1] from it and add additional overloads to the inherited class, to make it do multiple things.

[1]: https://delphisorcery.blogspot.com/2015/06/anonymous-method-...


Also not necessarily a function, both rust and js support closures in brackets.


A block introduces a lexical sub-scope but it's not a closure, because there's no closing over: the "closing over" part is about a function accessing its definition context during its execution. A block doesn't have such a distinction.


This closure https://en.wikipedia.org/wiki/Closure_(computer_programming)

Link because this describes it more thoroughly and more precisely than I can.


This both is and isn't true, and the finer points revolve around mutability/concurrency and implementation inheritance.

Modern computers are inherently concurrent and mutable, and these fundamental realities have been shown over and over again to cause hard-to-understand issues in sufficiently complex codebases. In the vast majority of OOP languages, instantiated classes are mutable-by-default and can be mutated by multiple concurrent operators (threads, coroutines, etc). This makes them actually quite similar to the underlying computing model - even though they're a high-level abstraction, they force you to deal with the knowledge that the memory at any address can be modified at any time with no warning. [1]

Classes in most languages also offer implementation inheritance, which boils down to an approach to "rebundling" the constituent functions in ways that are arguably simple enough, but generally in practice it has been found to be hard for programmers to reason about. Dynamic dispatch is very powerful, and the way that most inheritance mechanisms expose this seems to be a model that programmers understand in theory but fail to consistently remember in practice.

In my opinion, the fundamental difficulty with classes is that in most languages, you get all of these tools bundled (complected) into the same toolbox without any say in the matter. If you didn't want inheritance, too bad. If you didn't want concurrent mutability, you're now being asked to design a complex state machine to prevent it, rather than it being excluded off the bat by most functional approaches. Functional approaches force you to opt in to most of the footguns that are included "out of the box" in OOP.

Classes will remain the best approach in many OOP languages for RAII-shaped problems, where some part of an application needs to encapsulate and manage a limited, shared, and potentially mutable resource (such as a file handle, a database connection, or an in-memory cache), because classes are an abstraction that maps cleanly to shared mutable state with locking mechanisms.

[1] You can of course also shoot yourself in the foot with closures that themselves contain mutable state (e.g. some kind of mutable container), and I think this is a rarely-considered danger for functional paradigms in languages that default to mutable data structures.


Implementation inheritance is not "simple" at all. It's not just "rebundling" class methods, it allows the 'rebundling' to happen at some arbitrary place in the class hierarchy in a way that inherently involves dynamic dispatch/late binding for every method (virtual) call. (Unless the set of callers and derived classes is entirely known in advance and can be dealt with via devirtualization, of course.) That's a very loose semantics, and it's no wonder that developers find it "hard" to reason about.


I'm not sure you are really disagreeing. Which is to say I don't see where the not "isn't true" part come from.

Yes classes are closer to the physical reality of modern computer (and that is one reason I prefer C++), but they are still the same construct as a closure and vice-versa. Everything is just implementation details.

Again I grant that languages designed around classes (or really in my opinion, around the physical reality of the device implementing them) are more useful in practice. But from a conceptional abstraction point the post is true.


It's not true in the sense that classes are higher order functions + a lot of junk that the GP examined in some detail. All that junk adds a lot of complexity and makes classes significantly harder to work with.


This is one of the C++ features that I loved since the early days, the ability to control how immutable a class actually is.


Well said ledauphin!


OOP is an abstraction over functions, but it can be explained much more simply without closures. Classes introduce a convenient syntax / abstraction over polymorphism, and can be written with plain functions without the need for closures. Methods are just polymorphic functions that accept an implicit "self" argument, which is used to dispatch.

Taking the example from the article:

  function Person(firstName, lastName) {
    {
      type: "Person",
      fName: firstName,
      lName: lastName
    }
  }

  function getFullName(this) {
    switch(this.type) {
      case "Person":
        return this.fName + ' ' + this.lName;
      default:
        throw("TypeError getFullName is undefined for " + this.type ".")
    }
  }

  function setFirstName(this, firstName) {
    switch(this.type) {
      case "Person":
        this.fName = firstName;
        break;
      default:
        throw("TypeError setFirstName is undefined for " + this.type ".")
    }
  }
From here whenever a "method" is called, the program inspects the "type" attribute (this process is abstracted and hidden in OOP languages), and uses that to invoke the correct implementation of the method.

  let person = Person("John", "Foo");
  getFullName(person);


It's a cool idea, and I'm sure everyone gets a pleasant jolt when they make the connection (I sure did), but you probably already understood this idea at some subconscious or conscious level in order to get anything nontrivial done with a functional language.

It's one of the first things you're taught if you are learning a functional-at-its-roots language. Not JavaScript, which is mixed-paradigm at its roots. For me it was Scheme in college.

If you're learning a functional language, combining closures and functions that return functions to carry around "instance fields" and "instance methods" is what you learn right after you've gotten over the basics like map/filter and tail recursion. The equivalence of this pattern to classes is something you'll understand intuitively, even if you haven't put words to it yet.


> Not JavaScript

Funny enough, I actually had the referenced epiphany while learning JavaScript, since this idiom is so common in it.


Is it actually that common? Using a higher order function that returns member fields/methods which you then actually call?

Higher order functions are common in JavaScript, and closures are as well, but I don't recall seeing them used as substitutes for classes or vice versa for years at this point. With JavaScript you don't have to use closures and higher order functions to do modularization because you have classes, and you don't have to use the functor pattern (like Java before Java 8) because you have higher order functions.

ES5's syntax for object types looked a lot more like "closures with syntax sugar" than ES6 and later JavaScript syntax with explicit class declarations.


It's probably not common, but for me the answer lies here:

> I don't recall seeing them used as substitutes for classes or vice versa for years at this point

I had a fairly large school project developing a JavaScript application back before ES6, and kept searching for a better way to structure my code because I found it awkward using prototypes + binding this for events. Eventually I found a hidden golden nugget of a tutorial that showed me how to do it with closures and I was in awe that it somehow wasn't the norm (and obviously how cool it was to build objects out of functions!)


E6 “classes” are just functions that return objects.


You shouldn't analyse programming constructs based on their implementation in my opinion. Go down deep enough in the rabbit whole, and you'll see everything is just a Turing machine.

A more useful analysis in my opinion is to focus on the developer ergonomics of the language. That includes how the construct is used or can be used, any properties it exhibits or guarantees, what it prevents you from doing, etc.

From that lens, the ergonomics of a Class and a Function Closure are not at all the same.


Review:

Consider replacing:

  return function(method) { 
    switch (method) { 
      case 'getFullName': 
        return getFullName;
      case 'setFirstName': 
        return setFirstName;  
    }
  }
With:

  return { 
    getFullName,
    setFirstName,
  }
And leaving client code unchanged.

This leaves callers with a struct but is cleaner and more direct. So far as I can tell, modules and closure usage are objects in JavaScript.

I'm curious if you might have insight into what this approach might block other than exclusively using functions?


Hey erikerikson -- I think your solution is indeed equivalent, and I would say even more idiomatic js.

The reason I chose the first option, was to really bring home the point that all of this could be expressed with closures.


A class, generally speaking, is a binding of functions and data.

In the functional example, where does Person store the fName and lName variables? Is there an intrinsic/hidden 'this' pointer? If functions persist data, are they really classified as functions anymore?

The message passing part isn't very relevant, as the concept of calling a function pointer via string lookup is present in many dynamic languages (Ruby, etc).


No this is a really important question, and it has an answer.

They are called closures. They are "environments" in which variables live, each of which can have a parent environment dictated by whatever rules there are. (Some programming languages have it where every use of curly braces creates a sub-environment; in JavaScript originally only the function keyword would introduce a sub-environment and if you write `if (something) { var x = 123; }` then `x` belongs to the parent closure and it might contain `undefined` if the branch was not triggered. Since then JS has introduced let/const which are scoped to curly brackets.)

When you declare a function it still has access to the full space of variables which it was lexically defined with. If you mutate those with one function then another function which has access to that environment can see those changes, etc.


Good point, I guess what I was trying to get at is that an object is more equivalent to a closure (function + data i.e. local stack environment) than just a function (which executes with input parameters). The top-voted response put that more eloquently than I did.


The variables are persisted within a closure since they are in the scope of the internal function definitions. Whether each function points to the same closure value as they are changed would be based on the language semantics around the creation of the closure and whether mutability of the closure variables would propagate to the other function definitions that also have a closure around that variable (if the variables are updated by reference or value).


> In the functional example, where does Person store the fName and lName variables? Is there an intrinsic/hidden 'this' pointer? If functions persist data, are they really classified as functions anymore?

There is no intrinsic "this", the code as presented is using a closure. Closures are typically considered a standard part of functional programming.


Note that the example in the post only works because variables in the closure can be rebound. Not every language allows you to do that.


> In the functional example, where does Person store the fName and lName variables?

With a closure? And you get state hiding too.

> If functions persist data, are they really classified as functions anymore?

Sure, since otherwise returning a function from a function wouldn't be very interesting.


Actually, the more precise formulation would be: classes are related to functions as both are abstractions.

The author conveniently leaves out the differences: classes yield objects, whereas functions yield any kind of value. Classes are second-class citizens, whereas functions (usually) are first-class. Functions have a type, whereas classes (often) are (or are isomorphic to) one.


I don't get why so many JavaScript developers thing functional is cool. Functional has been around for 30+ years. OOP won against functional because OOP is easier to understand and leads to more maintainable code. These days people are taking a second look at functional because functional avoids state, which makes it easier to write concurrent code with no locks. (This has suddenly become important because Moore's law is coming to an end, and CPUs are adding more cores instead of making cores faster and faster.) But this advantage of functional is not applicable to JavaScript because there are no locks in JavaScript in any case!

Functional programming actually brings some benefits to backend code. To take advantage of the extra cores in a CPU you add threads, but if these threads are continually needing to lock in order to serialize access to shared state then concurrency is diminished. The more cores you have, the more threads you have, and then the more you have to lock. Functional style solves this problem by not having mutable state.

But when it comes to JavaScript these benefits don't exist. That's not to say there are no benefits at all to incorporating some functional style into JavaScript code, but wholesale replacement of OOP with functional style is not warranted.


Functional programming is getting popular in JavaScript not because of serializability of state changes across threads, but because immutable state and immediate-mode GUIs go hand in hand. React, as an example, works as an immediate-mode GUI (re-rendering VDOM on every state change) on top of a retained-mode GUI (the DOM). You manipulate your state using reducer functions and the library works behind the scenes to convert that to a UI. It alleviates all of the weirdness and performance issues of interacting with the DOM when state changes.

GUI programming is traditionally retained-mode. OOP is traditionally state encapsulation and mutability. OOP and retained-mode GUIs are conceptually similar, and Functional Programming and immediate-mode GUIs are conceptually similar.

Here's a good explanation of this: https://medium.com/@puppybits/immediate-mode-reactjs-7a6302c...


I don't see parallels between OOP and retained mode, and functional and immediate mode the way you are describing. UI components inherently have state. Pretending they can programmed in functional style with stateless functions is bizarre because UI functions do have side effects. See prior discussion: https://news.ycombinator.com/item?id=24363261


Yes, ui components have state in the case of a retained mode GUI like the DOM, but in the case of React, relevant data is derived from the application state (eg IsDarkMode is a state property used to derive colors for a component, or the checked state of a checkbox) and a unidirectional flow propagates that state change to the VDOM on every re render. Functional doesn’t mean stateless (state exists as closures within higher order functions, such as in the composition of functional components within React), it means side-effect-free, it means determinism in the derivation of additional state based on immutable state updates.


> Functional doesn’t mean stateless (state exists as closures within higher order functions, such as in the composition of functional components within React)

Being stateless and side-effect free is pretty fundamental to functional programming. If you can't replace a function call with its resulting value (without changing the meaning of the program) then it is not functional programming.

React is neither stateless nor side-effect free. Mapping it to concepts from functional programming is counter-productive. You get something that is neither fish nor fowl.


Not all OOP languages support inheritance, nor all FP languages are immutable by default, though.

GUI programming surely wasn't retained-mode on 16 bit home computers, in fact that was one of the issues getting used to GUI based OSes event queues back in the day.


> OOP won against functional because OOP is easier to understand and leads to more maintainable code.

You say that as if it's the truth. It's not at all clear to me that's the reason OOP "won".

> These days people are taking a second look at functional because functional avoids state, which makes it easier to write concurrent code with no locks.

I think that's only a small part of the reason for the interest in functional programming.


After picking up functional programming < 2 years ago and using it constantly at work, I definitely have to say I disagree with GP that OOP is easier to understand.

I think there's a lot of value in the concepts of OO's message passing, and exposing an interface that encapsulates some internals. It's a tool I regularly use in FP even with immutable types

Imperative OO to me though is plain difficult to do correctly, and shouldn't be the primary way that your encouraged to write code. It's much more difficult to track dependencies and who touches what in an imperative class, and you end up digging around the file before you can even know what you have to keep in your head.

With FP, you generally have pure functions that take in some state and dependencies, and produce a new state. Everything you need to know related to how that function behaves is explicitly given to you, and it's trivial to test the code. You can do this in imperative OO too, but you're nudged in that direction by default.

I do think there are cases when imperative code and imperative OO make a problem easier to represent/understand and faster to execute, but I like to keep those sections small and easy to fit in your head, delegating most of the logic to functions. The imperative OO is mostly some glue to bring the business logic to life.


OO: 'state is hard, let's make it easier by encapsulating it with only the procedures that need to manipulate it'

FP: 'state is hard, let's not do it unless we absolutely have to'


I think for FP it should be added, that the state is explicit and has a clear owner. Eventually someone has to replace the old value with the new value in the call chain

What's really interesting is Rust arrived at a similar destination, I assume as a natural byproduct of pushing for memory+thread safety. State isn't discouraged, but you get explicit owner(s) of data who lend it out to others who are readonly by default, and explicit when they will modify the reference you gave them.


Yeah, maybe it's more like "state is hard, so only Paul can do it. If you want to Paul to change some state for you, then you're gonna need to fill out this form."


Modern multi-paradigm language: "State is hard. Let's avoid it when we can and encapsulate it in procedures when we can't."


I don't think that OO code is easier to understand.

I do, though, I think, find OO projects easier to understand. There's an opinionated convention about what code to save in which file (one file for interface of a class, one for implementation).

Sometimes I think it's this, more than anything to do with language semantics, that actually drives OO adoption. When you sit down with an empty folder and a text editor, it's really easy to know where the files go.


I'd say OOP 'won' because Java offered garbage collection and a C style syntax. The fact that it was also OOP was incidental.


OOP first boomed with C++ before Java came around, and Java got GC from function languages.

If GC was the distinguishing factor, OOP would have never gotten off the ground.


Having been there when it all took off, it always feels so wrong to read "C++ code written Java style", when it was C++ that actually created that style.


Right—garbage collection makes most software engineering significantly easier.


> I don't get why so many JavaScript developers thing functional is cool.

Let me make a meta-point. It's natural to find yourself with a thought of this form: I don't get why so many think ___ is cool.

There are two answers to this:

1. None of them know something you know, and you are right.

2. At least some of them know something you don't, and you are wrong.

Statistically speaking, 2 is much more likely to be true. So when you find yourself having these kinds of thoughts, which are entirely natural, you'll make better progress if you can formulate it as a good-faith search for truth and approach it with an open mind.


>Let me make a meta-point. It's natural to find yourself with a thought of this form: I don't get why so many think ___ is cool.

I don't get why so many think cigarettes are cool.

You're telling me they know something I don't about cigarettes?


Yes, actually.

* Cigarettes are stimulants and feel great when they first kick in, like a good cup of coffee.

* Smoking gives you a culturally-approved reason to take breaks throughout the day and go outside.

* Smoking is a social activity that provides an easy way to meet new people.

Of course, it is strongly chemically addictive too with serious negative health consequences, but the reason people smoke isn't only addiction or no one would ever start. I learned the above list by once genuinely asking someone why they smoke.


Many/most who have a lifelong cigarette addiction would not describe them as “cool”.


> OOP won against functional because OOP is easier to understand and leads to more maintainable code.

OOP as we know it today, which is not what the inventor of the term had in mind, likely won because it was easy to add it to C in a manner compatible with low-level optimizations that were critical in application code at the time, and less so now.

I dispute that one style is inherently easier to understand than the other, though each has certain problem domains where it maps particularly cleanly.


Slightly tangential, but Single Assignment C is well suited to functional programming


> OOP won against functional because OOP is easier to understand and leads to more maintainable code.

A couple points:

I'm not sure whether OOP really won: pretty much any language now has taken lambdas & higher-order functions from FP; before those parametric polymorphism (ML was there before C++ templates and Java generics); many sources will tell you to limit mutable state; and even algebraic data types are starting to get mainstream traction (Rust). Speaking of which, I believe Rust does not sell itself as an OOP language?

It is not at all obvious that OOP is easier to understand. Those animal/shape hierarchies are indeed very easy to grasp, and they are also useless. Even games have moved away from class hierarchies, which are both slow and too inflexible. They're more about ECS/data orientation now.

I'm not sure that "easier to understand" does lead to more maintainable code either. If you're talking about daily use, why not. If this is a learning curve thing (and in practice I suspect it is), then I think ease of learning and code quality won't correlate with each other — provided only people who learned the stuff get to program with it of course.


> Those animal/shape hierarchies are indeed very easy to grasp, and they are also useless.

That's a wonderful description.


As someone that had some background in cladistics, I knew OOP was gonna get real complicated as soon as I saw animal hierarchies as a 'simple' example.


OOP won out over imperative because it fits very well to (desktop) GUI programming - UI controls maps elegantly to objects with state. OO turned out to be broadly useful, but GUI programming was definitely the driver.

I don't think anybody had compared maintainability between similar projects in OO and functional style. Functional was not really a contender in this space.

Almost everybody who adopted OO migrated from imperative. The popular OO languages were imperative languages adapted to OOP - Visual Basic, C++, Delphi etc.


Mutable state is also a readability concern. If you have to model the temporal state of some object in your mind, it is pretty prone to error as you change code.


> OOP won against functional because OOP is easier to understand and leads to more maintainable code.

Actually, OOP won because OOP is easier for non-developers to understand -- those who were making policy and purchasing decisions back in the 1990's.

And the OOP we now call "OOP" (as realized in languages like Java and C++) is not at all the OOP that was first envisioned by Alan Kay and implemented in languages like Simula and SmallTalk.


I think it's debatable that OOP is "easier to understand and leads to more maintainable code". It depends on size of project and experience of developers.

There are still certainly benefits to using FP in JavaScript from a design, readability, maintainability sense, even if you are not concerned with concurrent code with no locks. A good FP design can be easier to reason about than an OOP graph of objects.


> because OOP is easier to understand and leads to more maintainable code

How is OOP easier to understand exactly?

If you are debugging the code that looks something like this:

    class Child extends Parent {

      launchRockets() {
        this.launch(this.rockets);
      }

      render() {
        this.score = this.getScore();
        return `<span>${this.score}</span>`;
      }
      
    }
How do you know which part of your code contains implementation of this.getScore method? How do you know which part of your code is responsible for the number of this.rockets and how many methods in how many classes can change it? How do you find out which part of your code is going to call the launchRockets method, and whether this will even be this class's method or one of its children's? How many hops across your codebase do you need to make to figure all this stuff out?

How is this style any simpler than functional?


> How do you know which part of your code contains implementation of this.getScore method?

You place the cursor at 'getScore' and press F12. This problem (calling functions defined god-knows-where) is not unique to OOP as opposed to FP.

> How do you know which part of your code is responsible for the number of this.rockets and how many methods in how many classes can change it?

You place the cursor at 'rockets' and press Shift+F12. This problem (modifying/passing data around god-knows-where and what parts) is not unique to OOP as opposed to FP.

> How do you find out which part of your code is going to call the launchRockets method,

See above.

> and whether this will even be this class's method or one of its children's?

Well, there is Ctrl+F12 that shows you different (re)implementations of the method.

> How many hops across your codebase do you need to make to figure all this stuff out?

About three to four. Again, this problem is not unique to OOP as opposed to FP: with FP, when your 'foo' function gets a 'frob' function passed to it as a parameter, it's anyone's guess what actual function it may be. To find all possible cases requires you to find all call sites of your 'foo' function, but that's non-trivial since it is most likely has also been passed around as an argument under different names.


> This problem (calling functions defined god-knows-where) is not unique to OOP as opposed to FP

Well, part of that problem is solved by explicitly importing functions from individual modules, at which point you know where exactly your function is coming from.

> when your 'foo' function gets a 'frob' function passed to it as a parameter, it's anyone's guess what actual function it may be

Part of it is probably solved by static typing. The other part is that it shouldn't pose a problem, much like you probably don't have a problem passing all sorts of frobs to your map-s, and your filter-s, and your forEach-es, and your then-s


Not really, because good FP programming style makes heavy use of higher order functions.


You have IDE for that in general. If you are writing all code in Sublime, then yeah, you have a problem.


If you need an external tool to understand code it’s an indication that something is wrong.


I somewhat disagree. This will be true for pretty much any non-trivial code project. I'd love to see a language where I didn't need tools to understand what's happening, but that's impossible currently.

Do you really never need to see what a passed-in function actually does in your FP code? Never need to see what type an argument is in our fancy Haskell or Hindley-Milner-typed language?


I'd argue you're right, but when you're programming in something like Haskell that guarantees certain constraints are met just by glancing at declarations, these tools become less essential.

In FP, I tend to look at implementations to understand what they'll produce

In OO, I do this, but I also need to know _how_ since they might be changing the current context I'm working in. It's not enough to know that X method produces Y, I also need to remember that calling X implicitly modifies Z when I'm referencing Z

In an FP environment X can't modify Z outside of his context, so I don't have to care about how he produces Y. The cost of this is that someone needs to own Z and functions need to additionally return the new Z, but I think that explicit hierarchy helps in guiding towards a more understandable architecture


> Do you really never need to see what a passed-in function actually does in your FP code?

I'm hesitant to say always, but I can say:

- in imperative code I never feel comfortable without inspecting the passed in function

- in pure functional code, I rarely feel uncomfortable taking a pure function at it's word


If one refuses to adopt modern tooling it’s an indication that something is wrong.


Is that? If you don't write all your code with `cat`, is that an indication that something is wrong as well?


> Functional has been around for 30+ years.

60+, and OOP has been around for 50+.

> OOP won against functional because OOP is easier to understand and leads to more maintainable code.

OOP didn't “win”, it had a period of intense popularity starting in the late 80s and starting to fade around the mid-00s. And that was largely path dependence and building on C; had someone put the kind of effort into C-with-lambdas instead of Stroustroup's C-with-objects, we might have had a couple decades where the most popular languages were all C-descended impure functional programming languages rather than C-descended class-based OOP languages.


I would agree with you if JS wasn't JS. It is a terrible language to write OOP-style code in and a much better one to write functional-style code in. It is inherently a more functional language.


Callback hell and the inability to debug nested function calls without placing breakpoints when replacing loops doesn't sound super exciting either.


Callback hell? What is this, 2014?


> OOP won against functional because OOP is easier to understand and leads to more maintainable code.

If by functional you mean higher order as in the title and not what is commonly known as FP - it's never been true. For example, a function that has no outside side effect and that calls an anonymous function provided as an argument is way easier to understand, reason about and maintain than its OOP equivalent of creating an object and calling three methods in specific order mixed with your code.

> These days people are taking a second look at functional because functional avoids state, which makes it easier to write concurrent code with no locks.

Avoiding state doesn't make it easier to concurrently touch shared memory without locks. Event loop style concurrency simply doesn't touch shared memory concurrently, so it doesn't need locks and doesn't have all those multithreading problems. It can still mutate state left and right, higher order programming just allows to map intent better to the code, making "sequences look sequential" and all that.

Actually solving problems concurrently with high performance today generally requires ditching shared memory based synchronously communicating concurrency models, leaving async communications of isolated entities where locking problems and overhead don't exist. But it has nothing to do with functional programming, OOP, higher order programming.


Functional is making a comeback, because the performance is now good enough. That's my personal assessment. It never really "lost" to OOP, it just wasn't ever a real contender before, because of performance reasons.

With the revival of dynamic languages though, people heavily programming in JS, Ruby, Python, etc. Performance is no longer a concern of people. So now people are motivated by what makes the act of programming as convenient to the programmer.

When it comes to that, it turns out a lot from FP does make things simpler for the programmer. It started with data transformation, I think helped by the appearance of LINQ in C#, which first exposed people to functional data transformation. Then Java got Streams, and everyone kind of followed suit in introducing some higher order functions and map/reduce/filter and all that.

Also: callbacks. As asynchronous behavior became more popular, there was a real need to lower the amount of verbosity and just code you needed to type to program in an async style. So creating a class and an object for every little event in your app, to pass as callbacks to all async functions just got super annoying. So people introduced anonymous functions and lambdas, and added support for higher order functions that could be used as callbacks.

Once you've got callbacks and higher order functions, closures are a natural extension, you often want to pass a callback that will set some state elsewhere when done or other such things. So Closures were also added.

And here we are, with a full FP toolkit due to the fact that performance isn't a concern anymore and async programming is more convenient when your language has support for first class functions with closures and anonymous functions and lambdas.

At the same time, like you said, people started squeezing more out of their cores and their seperate IO controllers, so concurrent and parallel programming became more common. That forced people to look at the immutable characteristics of some FP langs, and so support for immutability was also added to a lot of language.

Once you're in the realm of immutability, Objects also become less useful, since there's no need to encapsulate and protect modifications to something you can't modify anyway. Thus once again, creating a class and a constructor and all that just becomes unneeded verbosity and extra code to write. So people wanted ways to go back to immutable value records and just write functions over them, etc.

All these things in my opinion combined for what is today the "resurgence" of FP.


It's worth noting that there are drawbacks to functional programming as well. It tends to result in a lot of copying, which is also not optimal for performance.


This is somewhat though not entirely solved by https://en.wikipedia.org/wiki/Persistent_data_structure


Another thing that people also miss all the time is that not all FP languages are immutable by default, most FP that achieve a certain level of mainstream adoption also have OOP like constructs, finally even Smalltalk-80 already provided the mechanisms to do LINQ like coding and passing closures around.


How no locks in JavaScript? Have you heard about async await?


Let's be honest. 90% of JS developers out there probably don't even know what a lock is.

I know a JS dev who gets paid good money to develop software, who thought the reason you shouldn't use floating-point to deal with currency was because "javascript is bugged".


Higher order functions are just a fancy way of writing classes.


You can implement an object-- a data abstraction technique where the said data (the attributes) is hidden behind a set of operations (the interface)-- with classes or high order functions or prototypes but that doesn't mean those are equivalent, especially software engineering wise.


Well, somewhat. Passing messages is inherently more dynamic, as you can see: you can change the string to anything at runtime, even something that doesn't exist, you can intercept the message and send it somewhere else or handle it however you wish. Static dispatch doesn't let you do this, but you generally get the advantage of faster calls and better compile-time diagnostics. In simple cases, the programming style can be similar, but for more advanced features they really differ in the kinds of things they support.


Maybe I'm confused by this particular example, but what languages would allow functions to store the value of the constructor like that without closures? The thing I don't like about closures is that they can get messed up easily, and not even all OO languages support them.

I often think of functions as intentionally "stateless", NOT persisting any value. For that a class is used, if anything for safety.


> Maybe I'm confused by this particular example, but what languages would allow functions to store the value of the constructor like that without closures?

It's javascript, where all functions are closures.


See also: Let Over Lambda for more on this.


+1. "Objects are a poor man's closure, and closures are a poor man's object."


I understand the amusement value of this quote, but I never quite understood what is so poor about a tuple of functions closed over same data into a closure. Apart from inheritance, what does it lose. In fact its good that it loses inheritance. Syntax of many OOP languages makes it way too easy to use inheritance -- common things ought to be easy and potential design mistakes hard.


> but I never quite understood what is so poor about a tuple of functions closed over same data into a closure. Apart from inheritance, what does it lose.

In the closure-based version, getFullName and setFirstName are defined anew for each instance of Person, using more memory each time. Typically isn't an issue unless you're creating a massive number of them, but the class-based version doesn't have this problem.

Also, just to throw it in the mix as well, the class-based version I believe is largely just syntactic sugar over the traditional way of creating objects in javascript, using prototype-based methods:

  function Person(firstName, lastName) {
    this.fName = firstName; 
    this.lName = lastName;
  
    return this;
  }
  Person.prototype.getFullName = function() {
    return this.fName + ' ' + this.lName;
  }
  Person.prototype.setFirstName = function(firstName) { 
    this.fName = firstName
  }

  const person = new Person("Ben", "Bitdiddle")
  person.getFullName()
Prototypical inheritance is interesting in that an instance of Person could be used as the class for another object, ad infinitum.


An optimizing compiler could easily detect that a bunch of closures share an upvalue block in an enclosing "constructor" and then instead make that block explicit, and rewrite an entire constructor in a way that doesn't involve multiple closures at all (given that escape analysis and lang rules allow that). E.g.:

  // optimized
  function constructor(...) {
    let this = {...}
    function method(this, ...) {...}
    return function (name, ...) {
      switch (name) {
        case 'method': return method(this, ...)
          // or return a temp closure
          // like it does in tfa

  constructor()('method', 42)
And vice versa, on a computer that does closures in hardware better than objects for some unrealistic reason.

That's why both are just abstractions, whose semantics may better fit one or another implementation, but it really doesn't matter for maths.


You don't lose inheritance. This is especially clear in Python, e.g.

    class Foo(Baz):
      def foo(self, bar):
        return self.baz(bar + 1)
Notice that we're not calling the 'baz' method via some fixed lexical variable; we're looking it up in the 'self' object, which has been passed in (implicitly) as an argument ('self' will also implicitly be passed into 'self.bar' as its first argument). Java's 'this' serves a similar role, except even more things are done implicitly.

One slight complication: 'self' should be lazy, so we can take the fixed point (so every method sees all of the overrides). This is easy in lazy languages (e.g. Nix); in strict languages we can just make 'self' a function: it looks up a given name in the object's properties/methods, and if not found it invokes the superclass (AKA 'super').


Poor poor man...


would any devs want to start a book club for books like this? the article mentions SICP, which is on my to-read list. it just seems so daunting. i'm currently reading practical object oriented design in ruby by sandi metz. would love to have somebody to discuss some books like this.


Tangentially related is the idea that some OO constructs can be represented as co-data. Previous discussion here https://news.ycombinator.com/item?id=24105755


Classes is a semantic way of organizing your zillion of higher order functions, that would otherwise be just floating around. Beans is the way to build higher order data. Mixing them with care is the art of programming.


God forbid the 'sin' function would be just floating around, outside of a static 'Math' class. Or 'reduce', for that matter—obviously you need a special 'Reducer' class for that.

That reminds me of Steve Yegge's "Execution in the Kingdom of Nouns", actually.


A math module or namespace would be nice. Also for C headers I'd like to do "from math.h import inf" to resolve conflicts with <cmath> and c++11 with non-compliant implementations.


Classes are extensible modules.

Although some people seem to like pre-history languages without modules that require workarounds like using prefixes everywhere.


that's one way of formulating it. another would be: classes are just a set of methods with a common set of parameters (constructur) already applied (partial application)


Isn't the function or OOO is just message passing?


I see classes as nodes in the power grid that transmits and transforms data instead of electricity. These nodes are wired up to other nodes via methods - their binding points. To be precise, a class is more like a spec that can be used to build an instance of that node. In some cases there's only one instance.

The main advantage of classes, I think, is that they define one underlying principle for all these data transformer nodes: that each node has a few binding points with certain interface: named methods. The nodes don't have to look this way, but restricting the freedom of design choices to this pattern makes it easier to compose many nodes together. It's like setting the expectation that all cars are squarish: it's easier to build the infra around cars when you know how they can look in general.


Ok. No. There's a lot of confusion with semantics. Following the most used definition of OOP as defined by JAVA, C++ and C#:

Classes are in no way functions in the FP sense because classes mutate state.

Classes are in no way functions because calling methods requires the instantiation of unrelated state. Example:

   class A
       def __init__(self, a, b, c):
          self.a = a
          self.b = b
          self.c = c
          self.banana = create_banana(create_gorilla(create_jungle()))

       def process_a(self):
          return process(self.a)
process_a cannot be used unless a, b, c and the banana plus the gorilla holding the banana and the entire jungle the gorilla lives in is instantiated.

Let's talk about terminology. There are three that often get mixed up.

   Procedure = a set of instructions that could encompass mutating and changing state.
   Function = a box that when given input produces an output. The box cannot modify the universe. It can only produce something new when given an input. 
   Object = A grouping of a set of procedures and state that can mutate. 
The javascript programmer OP is mixing up function and procedure. He defines a procedure and says that it can be a class. A procedure can be a class as it's just a set of instructions which can encompass defining scope, state and more procedures restricted to scope and state (aka an object).

This thing right here, written by the OP:

  function setFirstName(firstName) { 
    fName = firstName
  }
Is not a function... it is a method or a procedure that mutates state.

A function can do none of the above.

That's it.


You're making an argument from definitions that would exclude ML from being a functional language, which is absurd.


No I'm using the mathematical definition of a function. ML is not Math nor is it purely functional.

All functional languages need to bend the rules a bit in order to have side effects but in general the person is not writing functions and therefore is not getting the benefits of using functions.

FP is about reducing and restricting these functions that break the rules. Defining a class in terms of mutations and side effects is going against the philosophy of FP.


> No I'm using the mathematical definition of a function.

Which mathematical definition? A "box" is not a mathematical definition. There are multiple mathematically precise definitions of "function" possible. Yours is none of them. Some of them accommodate objects that model mutable state.

I don't think many (any?) PL theorists or functional programming practitioners would agree with your dogmatic stance. Where are you coming from with all this?


[dead]


You have missed the point.

It’s curious you think this piece is written by a “JavaScript programmer”, and the person who responded to you here is a “junior” — the implication being that you know more.

It might help to take a step back and assume intelligence in others: what if these people are smart? What could you learn from them? If you think the idea is wrong, assume it’s coming from a peer. One way you know you are doing that, is if you form your disagreement as a question.

I took the time to respond to you, because it looks like you have some real love for programming, but know less than you think. You can grow a lot more if you shift some of your thinking


>You have missed the point.

Tell me what you think the point is. Typically when someone thinks a point was missed they address the point rather than devolve the conversation into a condescending description about my character and how I should act. It's a sort of tactical what you're doing here. But who cares, no one is reading.

It's curious how you don't even address my points and just say that I missed the point when I'm the one dictating the point in my original comment. Typically this isn't how you respect other people and get them to respond positively to your comments, what you're doing will draw other people to support you but anger your target because of the condescension. I'm not sure what you're objective is because people aren't really reading this thread... it's already buried.

>It’s curious you think this piece is written by a “JavaScript programmer”,

The examples in the original piece are in javascript. Why don't you RTFA before commenting on the "point."

>and the person who responded to you here is a “junior” — the implication being that you know more.

The Junior thing was more of a retort. I said it with full awareness of what it implies and how the other person would take it because his tone obviously implies that he doesn't think he's a junior. Basically the replier has his own opinion which he is entitled to say (and that I respect) but when you say things like "dogmatic" and then vote me down... that's just not respectful. Since I can't vote him down I inject a bit of subtle rudeness in my replies as a subtle retort in return. Just being a mirror. Similar in a way to the subtle game you're playing. That's all this is.

>It might help to take a step back and assume intelligence in others: what if these people are smart? What could you learn from them? If you think the idea is wrong, assume it’s coming from a peer. One way you know you are doing that, is if you form your disagreement as a question.

Maybe you should take a step back and look at your own post. Telling people to shift their thinking implies you have a sort of superiority complex. As if you're not the one that's mistaken. How do you know I'm not a peer? How do you know I'm not superior. Perhaps you should take a look at yourself. You seem like a guy who thinks he's superior to me in programming but you haven't demonstrated as much, you haven't even addressed the point.

You went on a side tangent talking about my behavior. Trust me, if you want someone to change or listen to you, talking to them like this is not the path.

You want to know what will make people respond to you and listen to you? Address them without any subtle disrespect. It's very simple.

You could've responded to me like this:

   I disagree with you. I also think there was a misunderstanding of the point. Here's why...
By proving the other person wrong without being disrespectful you can change the other person's' character. But obviously this isn't our objective here is it?

That being said take a look at this wikipedia post and tell me. Who missed the point?

https://www.wikiwand.com/en/Functional_programming

   "Functional programming is sometimes treated as synonymous with purely functional programming, a subset of functional programming which treats all functions as deterministic mathematical functions, or pure functions. When a pure function is called with some given arguments, it will always return the same result, and cannot be affected by any mutable state or other side effects. This is in contrast with impure procedures, common in imperative programming, which can have side effects (such as modifying the program's state or taking input from a user). Proponents of purely functional programming claim that by restricting side effects, programs can have fewer bugs, be easier to debug and test, and be more suited to formal verification.[1][2]"
Literally that's the definition I'm following. It's one form of the definition and certainly a popular one. That's my opinion. You disagree? You think I missed the point? Elucidate. The original poster can disagree but my opinion is far from as he puts it "absurd."

Read the article. What definition of functional programming does that class with a setter even fit under? Ok he redefined it as a higher order function... Is what he doing procedural programming or functional programming. It looks to me he's doing a bit of everything and calling it a function. Does the guy even know that the class keyword is just sugar for "function" in javascript?

Look at this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Literally it says "classes" are just special functions. He's just reiterating the javascript spec as if it's a new fangled discovery. In fact, before ES6 all "classes" were written with the keyword "function." No dude. The article writer doesn't get it and neither does the person who responded to my post. When you do OOP with LISP you're not doing FP period. The article writer just noticed an isomorphism and is likely amazed by what all JS programmers were doing before ES6. Not to be insulting but the parent poster likely doesn't have much experience.

>I took the time to respond to you, because it looks like you have some real love for programming, but know less than you think. You can grow a lot more if you shift some of your thinking

I too took the time to respond to you. Despite your condescending attitude/game. Let's see where this goes.


If you decide to dismiss a position by saying that it chose the wrong definition, you're not contributing anything useful. "Pure" functional programming is not more functional programming than "Functional programming", the adjective specializes it and makes it more narrow. I don't really care what a wiki article says. I can run a survey of my colleagues if you want?

As for calling me a junior, well, you don't know how much experience I have, do you? It's probably more than you, though. I've used and participated in the development of functional programming languages for over a decade at this point.

Unsolicited advice: Sophomoric pedantry isn't a good look.


>Unsolicited advice: Sophomoric pedantry isn't a good look.

Starting conflict and calling people "dogmatic" and voting people down is not a way to win people over. I can see a number of paths this road could have gone down where you can easily get another person to agree with your ideas as an alternative viewpoint despite a wikipedia article saying otherwise. You chose to take none of these roads, maybe largely because you don't know how to do this.

>I don't really care what a wiki article says.

Right because your opinion is the best opinion and only opinion that matters. The community opinion and the majority opinion has no relevance to you because you participated in "functional development for over a decade." Whoop dee doo. I chose a definition of functional programming that has huge relevance to the community at large. You on the other hand... haven't even stated your definition yet.

Unsolicited advice: Arrogance and the inability to admit your own mistakes is not a good look. A decade of experience does not earn you the right to act like an ass hole nor does it make you a good programmer.


You chose a definition among many and then dismissed the submission as "confused" because it does not meet your definition.

I noted that your definition would preclude ML functions from being called functions. Your response was that ML wasn't purely functional, but that doesn't make it not functional. The pure in "pure functional" doesn't "to the exclusion of non-functional", it is simply a narrower category.

Considering that ML is one of the most impactful functional programming languages both in theory and practice, I think it's right to call that absurd and dogmatic. If you take it as a personal attack that I called your argument absurd, I don't know what to say. Maybe grow a little bit of skin?

> You on the other hand... haven't even stated your definition yet.

I don't need to provide a definition to find a flaw in yours. I did not come into this submission looking to smugly dismiss the topic at hand with my obviously irrelevant preferences for terminology. You did.


>You chose a definition among many and then dismissed the submission as "confused" because it does not meet your definition.

My definition? I quoted wikipedia. A lot of people use "my definition" Basically under "your" definition C++ and go might as well be a functional programming language.

>Considering that ML is one of the most impactful functional programming languages both in theory and practice, I think it's right to call that absurd and dogmatic. If you take it as a personal attack that I called your argument absurd, I don't know what to say. Maybe grow a little bit of skin?

Why grow skin? I literally didn't care. I just decided to call you junior because you were acting like one. It seems like you cared more. I was just explaining to the other guy why I called you a junior. I recognize the attack but I literally don't care, but that doesn't mean I won't respond or address it.

>I don't need to provide a definition to find a flaw in yours. I did not come into this submission looking to smugly dismiss the topic at hand with my obviously irrelevant preferences for terminology. You did.

My Definition doesn't have flaws. Like I said, if I expand it to encompass mutability (as you have) then you can place Go and C++ on the functional pedestal. I haven't used ML but clearly a language like ML only allows mutability as a small exception that's rarely used otherwise there's no point to classify ML as functional.

But let's get back on topic. You literally said that no PL theorist would agree with me. But look at wikipedia. Apparently many do. So you're wrong.


I don't take wikipedia to be authoritative about anything but I'll bite.

> Functional programming is sometimes treated as synonymous with purely functional programming, a subset of functional programming which treats all functions as deterministic mathematical functions, or pure functions.

Well, obviously in this context the author of the submission wasn't using that definition. So you coming in and dismissing the conversation because sometimes people mean "pure functional programming" when they say "functional programming" is clearly erroneous. I know when people make this conflation, often when they're talking about Haskell. When talking about ML, no one would take that synonym. Context matters.

I just looked, you provided a set theoretic definition of functions, the wikiwand one. You chose a poor definition for the topic at hand. In most mathematical settings it would be a perfectly fine definition, but not here. Context matters.

Here are the problems with it: It assumes an interpretation in sets when there are a plurality of interpretations of functions qua functional programming which are not compatible with set theoretic models. For example, functions in homotopy type theory have higher dimensional structure than just being elements of a set. Functions in domains have more structure than just the extensional mapping from inputs to outputs. Your chosen definition also includes non-computable functions which are simply not admissible in this context.

So your definition is simultaneously too restrictive and too lax.

You seem to be confused by the existence of multiple definitions and how they are appropriate in context.


>I don't take wikipedia to be authoritative about anything but I'll bite.

No but it's more authoritative than a random person on the internet (you). For things like this it's good enough. I'm not writing a paper here.

>Well, obviously in this context the author of the submission wasn't using that definition.

>Context matters.

>In most mathematical settings it would be a perfectly fine definition, but not here.

In this context it's also perfectly fine. OP did not make a distinction between subroutine or function. He also uses the term higher order function wrong. It shows two things. He does not know what a higher order function is and he does not know what a function is.

>Here are the problems with it: It assumes an interpretation in sets when there are a plurality of interpretations of functions qua functional programming which are not compatible with set theoretic models. For example, functions in homotopy type theory have higher dimensional structure than just being elements of a set. Functions in domains have more structure than just the extensional mapping from inputs to outputs. Your chosen definition also includes non-computable functions which are simply not admissible in this context.

This is the garbage pedantry that I warned you about earlier. I'm choosing the generally accepted academic definition. Hopefully you're not one of those people who can't comprehend "generally accepted" without an expose into axioms and different obscure branches of math.

>You seem to be confused by the existence of multiple definitions and how they are appropriate in context.

Find the generally accepted definition in wikipedia that this person is using (Hint: no actual technical definition exists). Obviously he's using "function" in place of procedure or subroutine. But then he uses the term "Higher Order" to imply an actual functions instead of procedures.

Not only does he use "Higher Order" incorrectly but his usage of the term indicates he doesn't know what a function is and the difference between a function and a procedure.

Yeah I'm not going to reply to your shit any more. HN really needs to change the policy of not letting parent posters vote others down.


> This is the garbage pedantry that I warned you about earlier. I'm choosing the generally accepted academic definition. Hopefully you're not one of those people who can't comprehend "generally accepted" without an expose into axioms and different obscure branches of math.

I think you should look into domain theory it is one of the most celebrated discoveries pertaining to the semantics of functional programming languages. And maybe you'll understand that from a programming point of view, what a function is is much more flexible and subtle than you think.

> Yeah I'm not going to reply to your shit any more. HN really needs to change the policy of not letting parent posters vote others down.

Would it help if I pretended that you downvoted me? Every time I see my imaginary internet points I will subtract 5.


No you should just stop voting me down. I'm literally going to go negative soon, I just joined. I literally cannot talk to you if you continue voting me down. It's bad form.

>I think you should look into domain theory it is one of the most celebrated discoveries pertaining to the semantics of functional programming languages. And maybe you'll understand that from a programming point of view, what a function is is much more flexible and subtle than you think.

Sure I could. But "context matters." This is not the topic of the conversation. There's a level of depth we're operating at here. Very few people understand "domain theory" and you shouldn't assume as much when you communicate with anyone.


Admittedly I downvoted this one: https://news.ycombinator.com/item?id=24438137

Yet again though:

"Random person (you)" "he does not know what a function is." "Yeah I'm not going to reply to your shit any more."

...give a good reason

---

Anyways, leaving this thread. I certainly learned something from it, I hope you did too! : )

I now noticed you're at -1, went ahead and gave you an upvote. Hopefully you can still post, and be a bit more aware.

--

Also, great work trying to explain bitdizzly. Def enjoyed reading your explanations, and will explore some the references you popped in


There's nothing for me to learn here other than domain theory which is very advanced math and how people behave.

If you follow the conversation you'll see that the entire argument turned into a battle of definitions with me turning to a very generally accepted definition of a function and bitdizzy turning to using more obscure mathematical definitions of "function" and arguments about semantics what context is relevant to what definition of "function." Whatever definition you choose to believe domain theory looks like very advanced stuff and clearly outside the scope of your average technical conversation.

Either way, I didn't have time to respond to your other comment below. But I will now.

Don't worry about the upvote thing. It's just for this thread I can switch accounts.


I'm not downvoting you. I can't downvote anyone. I don't think I have enough points to downvote. If I didn't want to talk to you I wouldn't be replying!


> It's curious how you don't even address my points and just say that I missed the point when I'm the one dictating the point in my original comment.

> Typically this isn't how you respect other people and get them to respond positively to your comments

> The examples in the original piece are in javascript. Why don't you RTFA before commenting on the "point."

I wrote that article, so in effect you are responding to my original comment : ).

Fair that what I wrote was condescending. I purposefully wrote it that way, as a second meta-point: to get you to experience what it feels like when people assume negative, and use words like "Ok, no", "Javascript programmer" (negative connotation), "junior"

With that accomplished, moving on to more civil ground.

> I'm not sure what you're objective is because people aren't really reading this thread... it's already buried.

First, as you can now feel, I was pretty annoyed by your presumptuous tone in your initial comment. Second it really did feel like you had depth and love in programming, but from your comments I intuited some traits that lead to lower potential outcomes. Mainly: jumping to conclusions too quickly (harder to learn new things this way, and unless you are almost finished with your career, it can be detrimental).

> Does the guy even know that the class keyword is just sugar for "function" in javascript? ... Not to be insulting but the parent poster likely doesn't have much experience.

Yes, I do know what classes de-sugar to in javascript, I worked with many of the folks who brought classes to the ES spec.

As you most likely know, javascript classes are a bit of a special case, because JS uses prototypical inheritance. Classes in other languages do not de-sugar this way.

For example: you can see a function as a special `class` with only one method `apply`, or you could see a class as a special higher order function.

> Who missed the point?

The point is, that though functions and classes seem distinct, they are less distinct than one would think. In your comments, you seem very attached to the specific distinctions you've set up with classes, procedures, and functions. One question I would ask you is: what is _similar_ about them? Could you express all of these with one abstraction? Is there a difference between choosing `class` as the base abstraction, vs a closure?


>One question I would ask you is: what is _similar_ about them? Could you express all of these with one abstraction? Is there a difference between choosing `class` as the base abstraction, vs a closure?

The answer is yes you can. But you're thinking in the wrong direction.

First let's make things clear. A function is just a one line expression where all values are immutable. There is no concept of time or procedures in a function.

function

  def f(x):
     return ((x + 1) * 2)/3

procedure:

  def f(x):
     x = x + 1
     x = x * 2
     x = x / 3
     return x
You are saying that a procedure is isomorphic to an object which is to say you can define an object in terms of a procedure and a procedure in terms of an object. People often mix up the two terms, procedure and function but it's no big deal. The issue is, you used the term "Higher Order" function and the second issue I had was that it's actually quite an obvious thing presented as if it was novel.

Well first off all computers are procedural machines. The underlying machine is just executing machine code which is a series of procedures. So all programming languages on top of this are implemented with procedures.

That means OOP languages are implemented with procedures.

That means FP languages are implemented with procedures.

That means procedural languages are implemented with procedures.

Basically your post is just reiterating how an object is implemented by a compiler.

However the title of your post used "Higher Order Function." This implies you don't know the distinction between procedures and functions as "Higher order" functions are usually reserved for true functions, not procedures.

That was the point of my post, to help you see the distinction because although you can create objects from procedures, you cannot create objects from functions because objects require mutation which is a primitive that a function cannot actually provide. The relationship is surjective or unidirectional.

>harder to learn new things this way, and unless you are almost finished with your career, it can be detrimental

This is the internet. I can communicate without a filter on the internet with little consequence. Filters slow things down as do a lack of filter. Either way I can get to the point quickly without worrying about offending someone.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: