keean / zenscript

A trait based language that compiles to JavaScript
MIT License
42 stars 7 forks source link

Functional Programming vs. OOP #43

Open shelby3 opened 5 years ago

shelby3 commented 5 years ago

Functional Programming vs. OOP

@NodixBlockchain wrote:

Functional language are stateless,

Incorrect. Imperative state effects are managed in the monad paradigm of pure FP.

There’s the Optionally declare functions pure? issue thread #4 for discussing monads and a general Language Discussion thread #1. There’s also a discussion of pure effects systems in the latter portion of the Concurrency issues thread #17.

and i dont see well how stateless program can be converted to goroutine and channels efficiently.

I already told you that the #35 issues thread (where this discussion originated) is not about creating a pure FP language.

But maybe i'm missing something or you have another definition of FP.

I already gave you the superset definition of FP:

Go has first-class, higher-order functions, closures, and GC. Therefore it is a FP language. Just not a pure FP language.

I really grow weary when having discussions with you because it’s as if the relevance of what I write doesn’t sink in for you the first, second, or even sometimes the 3rd or 4th time it is explained. How many more times do I have to explain that pure FP is not all of FP.

For me where i put the frontier between FP and imperative is statfullness or not.

FP is not about avoiding state. That seems to be the stumbling block of your persistent misconceptualization.

FP is about the definition I quoted above.

OOP binds data to methods in objects at instantiation and includes the anti-pattern of virtual inheritance. FP employs dependency injection of functions along with data that function call, which is late binding of interface to data (aka “interface inheritance”). Typeclasses automate (and make implicit) this FP capability.

So the reason you do not comprehend the significance of what we are doing is because you haven’t yet grokked the distinction between FP and OOP. And how typeclasses are simply an automation of FP late binding dependency injection.

The strictly pure (and lazy evaluation) variant of FP is more focused on the mathematical definition of a function and equational reasoning. Monads are employed in order to model stateful effects in the pure FP paradigm.

P.S. I know you are a highly productive programmer once you understand. So I hope we could attain mutual understanding.

shelby3 commented 5 years ago

@NodixBlockchain wrote:

The thing about functionnal programming being stateless and pure is not a matter of religion, it's a matter of what are the practical advantage of using functional programming, that you already talked about in all length in many threads.

I have hard time to grok the point to do functional programming while discarding all the things that make functional programming interesting.

Higher order function and first class function are present in any procedural language. But what make this especially interesting in the context of functional programming is absence of side effect, which allow composability, easy parrallelism etc.

If you remove all the 'religiousity' of functional programming, then you discard all its advantage in the same time. Then can wonder what advantage you really expect out of it.

Did your eyes entirely miss what I wrote:

OOP binds data to methods in objects at instantiation and includes the anti-pattern of virtual inheritance. FP employs dependency injection of functions along with data that function call, which is late binding of interface to data (aka “interface inheritance”). Typeclasses automate (and make implicit) this FP capability.

So the reason you do not comprehend the significance of what we are doing is because you haven’t yet grokked the distinction between FP and OOP. And how typeclasses are simply an automation of FP late binding dependency injection.

Additionally, local assumptions can be immutable, which does not require the global, total purity and religiosity of Haskell. Partial orders instead of total orders, because the latter don’t exist in our universe.


Closure can be a good thing, but in the context of Go, that can also turn into a can of worm, because closure can be considered as first class function, except they contain hidden dependencies, and thus potential side effects etc that can create trouble with concurency. And they can easily also mess up the heap. After why not having them, they can be useful for certain things.

We already addressed that.

GC is orthogonal to the programming style.

Not true. The HOF (higher-order functions) essentially require GC.

As for the issue of late binding and dependency injection, i can also totally grok this, except using typeclass for this seem to be a weak form of it.

If you can grok it, then why are you thinking non-pure FP has no purpose. Typeclasses are a disciplined, well-typed form that leads to good abstraction. Good abstraction is essential to expert programming.

You do not place a high priority on abstraction. We do.

Dependency injection in the strong form can allow to load a specific implemention of a service or whatever else at runtime based on a config file for example.

It's what you can get with C# + COM with the Object thing that can be assigned any implementation of anything at runtime based on runtime conditions.

It's what i can get with my system too, except i choose a different path, mostly because i had a massive overdose of C++ templates after doing many project with ATL/NPAPI, Direct Show, and other things, so i made my system to have the equivalent of C# / COM dependency injection, but with very slim runtime and portable. But it's really because i grew a huge aversion of C++ type system. Same on a note, i'm not especially a C purist, but all the other language i tried ended up letting me down on a thing or another.

Using typeclass for dependency injection already limit the cases to compile time dependency injection, or then need the dictionary injection thing, but i still wonder the limit of this system in the context of dependency injection / late binding, if it's possible to have a system of plugin loaded dynamically at runtime based on typeclass definition, that can be portable and consistent across different compilers etc.

After i can get the interest of typeclass, in the sense of programming in term of "concepts" that can be applied to many situations and types, especially when it's done at compile time where the compiler have all the informations about the types and how to deal with them and overcome the limitation of OOP. I can completely get the interest of this.

But it seem to me to be still a weaker form of dependency injection / late binding.

I’ll let @keean address this. I am growing weary.

shelby3 commented 5 years ago

I think I figured out why you’re downplaying the typeclasses point and not grokking how FP + goroutines work together.

I will just add that it seems you do not see how Go’s goroutines can be combined with FP, because you presume that concurrency/parallelism in FP is only obtained as a total order of purity and immutability. But there is no such thing as 100% parallelism. In a pure FP language we would use monads to structure state so that we can prove which parts of our program are mutual independent so we can parallelize them. We this is analogous to employing local immutability and purity annotations and running the independent parts in goroutines.

You think too simplistically and do not see that they are dualistic analogs of each other. There is no such thing as an entirely immutable program which is also Turing complete. Rather you’re thinking of a declarative language with no state transformations such as HTML without scripting.

You’ve been hoodwinked by Haskell into thinking it’s doing some kind of exclusive magic that doesn’t have any mutable state. Or that the global purity and laziness as the default in Haskell does not have a categorical dual (i.e. impurity and eagerness as defaults) which is equivalent in power and also employs FP. Both of the categorical duals can both implement the capabilities of each other. They just have different default models due to the defaults chosen.

sighoya commented 5 years ago

@shelby3 wrote:

The compiler can easily offer both and convert that dot notation syntax sugar into:

But how do you solve this:

typeclass C1[A,B]
    a:self->A
    b:self->B

such that can write record.a, record.b instead a(record), b(record) .

And what about copying a value from records to other records, just like we importing values from modules to other modules?

keean commented 5 years ago

@sighoya

I don't understand, you can simply write:

typeclass C1(S, A, B)
   a : S -> A
   b : S -> B

a(record) -> record.a() is simply a syntax change, putting the first function argument before the function name.

I don't see the issue that is concerning you.

sighoya commented 5 years ago

a(record) -> record.a() is simply a syntax change, putting the first function argument before the function name.

So you request for an uniform function call syntax. I think this is quite ok.

What about default values in typeclasses and importing values from other records, do you want to realize that by manual copying?

keean commented 5 years ago

@sighoya

What about default values in typeclasses and importing values from other records, do you want to realize that by manual copying?

Personally I want instances to be non-overlapping, as overlapping can cause problems with the meaning of programs changing when new instances are introduced, which I see as a mis-feature. This means default values are only allowed if the typeclass has no instances. This is not a very common case, and is probably not at all useful, so I don't think default values should be supported.

I favour using disequalities to make make the same choices without overlapping, so:

typeclass Negatable(A)
   neg(A) -> A
instance Negatable(A) requires A == Bool
   neg(x) = not(x)
instance Negatable(A) requires A != Bool, AdditiveMonoid(A)
   neg(x) = -x
shelby3 commented 5 years ago

@keean wrote:

Personally I want instances to be non-overlapping, as overlapping can cause problems with the meaning of programs changing when new instances are introduced, which I see as a mis-feature.

An alternative is all instances would be manually listed on import. So then no strange changes based over defaults overlapping when imported modules change.

Another alternative is only instances not defined in the module their data types have to manually listed on import.

Another alternative to add to the second one above is for the compiler to create a metadata file with a list of the last imported instances that weren’t from same module as their data types. The programmer would need to manually augment this metadata or delete it so the compiler could refresh.

shelby3 commented 5 years ago

OOP is really Actors not Class Inheritance

sighoya commented 5 years ago

OOP is really Actors not Class Inheritance

Yes, the original idea of Kay was to model entities as "objects" communicating over channels "messages".

BTW, this might interests you, too.

sighoya commented 5 years ago

Note, if I import a struct in Rust which has implementations, I also get the implementations into the current scope. I can implement new methods in the current module for the same struct which can then be exported to the module importing the said struct from the current module. This provides in effect inheritance of behavior, similar to extension methods, but the implementation of them may differ.

Otherwise, inheritance of signatures in OOP is not that good, as it leads to collision just like importing module members at once "open".

In the end, I would not say that inheritance in OOP is the biggest problem, there are more annoying ones:

shelby3 commented 5 years ago

BTW, this might interests you, too.

Ty. I actually linked that before in these 1000s of Github posts, but lost it.

shelby3 commented 5 years ago

I believe I refuted every point in favor of Scala’s OOP in In Defense of OOFP.