fantasyland / fantasy-land

Specification for interoperability of common algebraic structures in JavaScript
MIT License
10.11k stars 375 forks source link

Remove all OOP features #329

Closed Ne4to777 closed 2 years ago

Ne4to777 commented 3 years ago

Hi! I really don't understand why programmers need OOP features in FP (like this, new and оthers). Looks like you don't believe in the FP paradigm and this specification is written for educational purposes only

Avaq commented 3 years ago

I really don't understand

Fantasy Land specifies a simulation of type classes (an FP feature) in JavaScript. The closest it can get to functional type classes in a language which doesn't support them natively, is to specify classical interfaces (in text, not code, because JavaScript also doesn't have interfaces).

These classical interfaces are most easily and conveniently implemented in JavaScript via classical classes (using this and stuff), but they don't have to be. There are various libraries out there which have values that conform to these Fantasy Land interfaces which were constructed without the use of new or this.

For example, Sanctuary uses Object.create to create object structures like:

{
  value: 42,
  __proto__: {
    isJust: true,
    'fantasy-land/map': function(f){ return Just(f(this)) }
    //...
  }
}

So Sanctuary doesn't use classes or new, but does use this.

It's also possible to conform to the Fantasy Land interfaces without using any this or new:

const Identity = value => ({
  value,
  'fantasy-land/map': f => Identity(f(value)),
  constructor: {
    'fantasy-land/of': x => Identity(x)
  }
})

So Fantasy Land does not inherently condone or condemn the use of such features, but having to simulate type classes through interfaces leads to a solution which naturally aligns well with the classical object oriented model. As a library author, you can choose to benefit from this alignment, but you don't have to. Finally, users of Fantasy Land compliant libraries don't typically notice either way: as a consumer of a FL-compliant lib you don't need to use any OOP features.

Ne4to777 commented 3 years ago

Well, even if all the developers of FP libraries write in OOP, why should other developers use FP? Therefore, the total defeat of the FP on all fronts is evident. The only thing that is widely used is composition. I've written a couple of examples, so what's the problem?

// of :: Applicative f => a -> f a
export const of = x => () => x

// extract :: Comonad w => w a ~> () -> a
export const extract = wa => wa()

// map :: Functor f => f a ~> (a -> b) -> f b
export const map = fa => g => of(g(extract(fa)))

// contramap :: Contravariant f => f a ~> (b -> a) -> f b
export const contramap = fa => g => of(x => extract(fa)(g(x)))

// ap :: Apply f => f a ~> f (a -> b) -> f b
export const ap = fa => fg => of(extract(fg)(extract(fa)))

// chain :: Chain m => m a ~> (a -> m b) -> m b
export const chain = ma => g => g(extract(ma))

// extend :: Extend w => w a ~> (w a -> b) -> w b
export const extend = wa => g => of(g(wa))

// compose :: Semigroupoid c => c i j ~> c j k -> c i k
export const compose = cij => cjk => of(x => extract(cjk)(extract(cij)(x)))
davidchambers commented 3 years ago

Well, even if all the developers of FP libraries write in OOP, why should other developers use FP?

Many people find the functions provided by Sanctuary and similar libraries quite natural:

> S.map (Math.sqrt) ([1, 4, 9])
[1, 2, 3]

> S.map (Math.sqrt) (S.Nothing)
Nothing

> S.map (Math.sqrt) (S.Just (64))
Just (8)

> S.map (Math.sqrt) (S.Left ('XXX'))
Left ('XXX')

> S.map (Math.sqrt) (S.Right (100))
Right (10)

> S.map (Math.sqrt) (S.Pair ('foo') (1024))
Pair ('foo') (32)

Someone using S.map need not be concerned with its implementation.

Ne4to777 commented 3 years ago

Well, even if all the developers of FP libraries write in OOP, why should other developers use FP?

Many people find the functions provided by Sanctuary and similar libraries quite natural:

Someone using S.map need not be concerned with its implementation.

These are particular examples. And there really is nothing criminal about them. But can you show an exemplary FP program? So far, I only see terrible OOP-FP mutants.

davidchambers commented 3 years ago

I'm pleased that you do not find Haskell's fmap function criminal. :stuck_out_tongue_winking_eye:

You may find concatFiles and program (and their surrounding code) interesting.

Ne4to777 commented 3 years ago

you held on well, but then something went wrong :) https://github.com/sanctuary-js/sanctuary-site/blob/ac68f7cbd1c541cfa653089b815cc4e1ae08b3b5/scripts/generate#L340

Аnd this is why "reduce" will always be a problem: https://github.com/sanctuary-js/sanctuary-site/blob/ac68f7cbd1c541cfa653089b815cc4e1ae08b3b5/scripts/generate#L568

davidchambers commented 3 years ago

Аnd this is why "reduce" will always be a problem

Why is that? It's not obvious to me.

Ne4to777 commented 3 years ago

Аnd this is why "reduce" will always be a problem

Why is that? It's not obvious to me.

the code inside was difficult to write through composition, so you wrote it imperatively. Therefore, the FP dies.

bergus commented 3 years ago

It's really unclear why you are contrasting the usage of objects with functional programming as if they were mutually exclusive.

Ne4to777 commented 3 years ago

It's really unclear why you are contrasting the usage of objects with functional programming as if they were mutually exclusive.

it's not just about objects, but about the context of objects. In js, there are three ways to pass variables into a function: arguments, global variables, and context. Functional programming is just arguments and nothing else.

MarisaKirisame commented 3 years ago

Аnd this is why "reduce" will always be a problem

Why is that? It's not obvious to me.

the code inside was difficult to write through composition, so you wrote it imperatively. Therefore, the FP dies.

I am very confused about the flow of the conversation. For starter, ultimately, if you find some code unsatisfactory, you... fix the code and open a pull request?

Another thing is, I dont think "the code is in x style" is a good argument. people write code as an mean, not as an end.

"The code has error/is complex/inefficient/make reasoning hard because it screw up invariance“ is some of them, but "the code is written in imperative style" isnt really one IMO. (yes, mutable reference screw invariance and reasoning hard, but the code you object to is only using local variable). You can rewrite it with state monad or with recursion in general... but the code is "the same" - you only push the reasoning complexity inside the monad/the call stack.

And at the end of the line, "all code should be written using FP" is just a weird assumption. There are a plethora of domain out there, that can be written by one single person, but the SOTA isnt written in FP. Some example is chess engine, egraph, linear algebra, HPC. If fp die here, fp is dead everywhere.

puffnfresh commented 2 years ago

Functional programming can use this and new. They don't break functions.

They may or may not be "OOP features" - whatever that means. Nobody cares.

Ne4to777 commented 2 years ago

Functional programming can use this and new. They don't break functions.

No, it can`t. Otherwise call it FOOP.

CrossEye commented 2 years ago

@Ne4to777:

Care to give the definitive definition of FP and OOP and then use them to explain why FP code cannot interact with OOP code?

Аnd this is why "reduce" will always be a problem

Why is that? It's not obvious to me.

the code inside was difficult to write through composition, so you wrote it imperatively. Therefore, the FP dies.

JS's poor recursion support means that many things cannot be written internally as cleanly as they might be in, say, Haskell. That doesn't mean they don't support an API that is FP.

Ne4to777 commented 2 years ago

@CrossEye , what do you mean by poor support?

CrossEye commented 2 years ago

@Ne4to777: Are you simply trolling here?

It's well-known that JS has low recursion stack limits. Last I knew (maybe 2019) most browsers were in the 10,000 - 20,000 call range, except that Safari went to 30,000. Moreover, although tail-call optimization has been specified since ES2015, almost all engines are ignoring this. So one cannot run recursive code against large data. And even against smaller data, the overhead is often performance-prohibitive. (For a case study, see Eweda, the predecessor to Ramda, which had elegant recursive code but couldn't perform.)

Recursion is a much cleaner way to write FP code; it can be done without variable reassignment, and often much more simply than its imperative alternatives. But since we cannot use it in JS unless we know the data is fairly small, we can't use it to build libraries.

Ne4to777 commented 2 years ago

@CrossEye , You said about browsers, but what does this have to do with javascript? Or then show me infinite recursion in Haskell in any browser.

We are discussing the concept, not the implementation. The scarcity of resources today should not kill the theory of tomorrow.

You put the question like this: why do we need to use JS because it is bad in FP? And I’m like this: since we are using JS in FP, then why we break the rules that we can not break?

bergus commented 2 years ago

@Ne4to777 You seem to be missing the fact that the fantasy-land interoperability specification is, despite the name, no pipe dream of tomorrow but rather a practical tool for today. If you want to discuss theoretical concepts, there are better venues than this issue tracker.

Ne4to777 commented 2 years ago

@bergus , that is, is it okay to use context in a contextless paradigm?

CrossEye commented 2 years ago

@Ne4to777:

We are discussing the concept, not the implementation. The scarcity of resources today should not kill the theory of tomorrow.

I have no idea what you're discussing. Most of us here are discussing a specification for data types with useful commonalities.

Moreover, a specification that cannot be implemented due to practical concerns is simply a work of fantasy.

bergus commented 2 years ago

PLONK