News for hackers. Stuff that matters.
Re:When FP? And When OOP? (2013) by alkonaut on Monday January 28, 2019 @05:34AM (#19016420)
I think these concepts are often conflated. One thing I miss most in OOP and appreciate most when doing FP is ADT's, when I can represent

  Type payment = Invoice(Address) | Card(CardDetails) 
And then match/switch on those things, while the compiler will tell me if I did something wrong such as not handle one case. Adding a third type of payment (Cash) is as simple as adding "| Cash" to the definition - and the compiler will tell me exactly where I haven't handled cash payments.

A simple smell test for a programming language is this: if describing the above takes more than the above code - your language has a wart. If you try to describe this properly in C# you'll be writing an abstract outer class and N concrete inner classes, plus boilerplate for comparisons and so on. You'll be looking at likely 50-100 lines of code where no single line of code actually shows whan what you are modeling. This is the terrible part of OO. I'm sure there are a few cases to show an FP weakness handled elegantly by OO - but I'm equally sure those cases are fewer.

Also, there is nothing particularly "FP" about ADT's. They are just common in FP languages. Any OO language with a sum type and exhaustive matching can do this. But typically, such as for Java, C++ and C# they don't (yet). There are languages that support this that aren't FP (but not necessarily OO either) such as Rust and Kotlin.

Re:When FP? And When OOP? (2013) by chriswarbo on Monday January 28, 2019 @06:44AM (#19016709)
> Adding a third type of payment (Cash) is as simple as adding "| Cash" to the definition

This is the "expression problem" https://en.wikipedia.org/wiki/Expression_problem

In FP with ADTs, anyone can write new functions for a datatype. Yet, as you say, adding a new case requires modifying the definition and existing functions.

In OOP, anyone can add a new case, by defining a new subclass. Yet writing a new method for that class requires modifying the definition and existing subclasses.

One of these isn't always better than the other, so it's useful to have both.

> If you try to describe this properly in C# you'll be writing an abstract outer class and N concrete inner classes, plus boilerplate for comparisons and so on.

This overhead is due to emulating one approach using another. It would also take a bunch of boilerplate to implement subclassing with ADTs (e.g. a sum type to describe the methods, a smart constructor to implement the inheritance, etc.).

(Personally I prefer ADTs too, but they're not a magic bullet)

Re:When FP? And When OOP? (2013) by unhammer on Monday January 28, 2019 @09:07AM (#19017479)
OCaml has ADT's, but also polymorphic variants[0] which are open, and let you say "these alternatives or more", or "these alternatives or less": https://v1.realworldocaml.org/v1/en/html/variants.html#polym...

It's the main thing I've missed from OCaml when programming in Haskell (you can sort of do something like this with typeclasses, but I much prefer the OCaml solution).

Re:When FP? And When OOP? (2013) by dan-robertson on Monday January 28, 2019 @10:40AM (#19018254)
Ocaml also has open variants where one can add cases to an existing sum type (essentially this is like exceptions but less special) and it’s objects are like a dual of polymorphic variants where you get a record which you may add fields to. Neither of these features is as commonly used as polymorphic variants however.
Re:When FP? And When OOP? (2013) by pdpi on Monday January 28, 2019 @07:14AM (#19016875)
Haskell's typeclasses (or, equivalently, traits in Rust) help a lot with this problem, though. It's one of the biggest language features I wish I had access to in more languages.
Re:When FP? And When OOP? (2013) by chriswarbo on Monday January 28, 2019 @12:04PM (#19019055)
Haskell type classes are great for most situations, except when they fail it can be quite spectacular. I think the "magic" of type classes comes from two things:

- All instances are thrown into one global namespace, so we don't need to worry about where to find the right one.

- There can be at most one instance per type. Hence there's no ambiguity about which one to pick.

These make it easy for the machine to pick the correct instance automatically. Unfortunately, it also means that every time we import or upgrade a library, there's the chance that it will break things by exposing an instance that clashes with what we have.

The "backpack" stuff looks interesting in this regard, although it seems very over-engineered (mostly due to legacy concerns).

Re:When FP? And When OOP? (2013) by 0db532a0 on Monday January 28, 2019 @02:35PM (#19020343)
What is backpack stuff?
Re:When FP? And When OOP? (2013) by sideeffffect on Monday January 28, 2019 @07:39PM (#19022643)
Re:When FP? And When OOP? (2013) by 0db532a0 on Tuesday January 29, 2019 @12:33PM (#19028163)
Oh thank goodness. Finally.
Re:When FP? And When OOP? (2013) by thesz on Tuesday January 29, 2019 @09:13AM (#19026385)
You are talking about expression problem: https://en.wikipedia.org/wiki/Expression_problem

"Extend program with new data types and new functions"

It is quite well studied and solved in various languages. And, in my opinion, typical OO language solution does not come as especially succinct.

Re:When FP? And When OOP? (2013) by chriswarbo on Wednesday January 30, 2019 @12:55PM (#19037417)
> It is quite well studied and solved in various languages

I wouldn't say the expression problem is "solved"; various languages pick different tradeoffs, but the underlying problem is still there.

The best description I've seen of this is at https://www.tedinski.com/2018/02/27/the-expression-problem.h...

Re:When FP? And When OOP? (2013) by AnaniasAnanas on Monday January 28, 2019 @09:12AM (#19017512)
> Yet, as you say, adding a new case requires modifying the definition and existing functions.

The same is true in java if you use instanceof. The thing is, just like enums inductive data type definitions are not supposed to be extended by any module other than the one declaring them, and adding more should be considered an API change.

Re:When FP? And When OOP? (2013) by chriswarbo on Monday January 28, 2019 @11:53AM (#19018972)
> The same is true in java if you use instanceof.

Java isn't a particularly pure example of OOP: it has `if`/`for`/`while`/etc. from structured programming, non-OO "primitive types", non-polymorphic `+`/`-`/`*`/etc. and so on. `instanceof` is another example of a non-OOP feature (the OOP approach would be an `isFoo` method, where each class can decide what to return).

> inductive data type definitions are not supposed to be extended by any module other than the one declaring them, and adding more should be considered an API change.

Exactly. Yet in OOP, anyone can add new subclasses whenever they like, without ever telling the original author, and instances of those subclasses can co-exist alongside any other subclasses and will (in theory) work fine with all the existing code written for the superclasses.

What if I altered your sentence to say the following:

"Functions acting on a datatype are not supposed to be extended by any module other than the one declaring that type, and adding more should be considered an API change."

This would seem crazy if we applied it to an FP language; yet that's exactly the case in OOP, where a fixed set of methods are implemented inside each class.

Re:When FP? And When OOP? (2013) by jcadam on Monday January 28, 2019 @08:05AM (#19017113)
I'd argue that OOP has a place when you're doing modeling and simulation work. What you should never do is OOD - crafting your entire software architecture as a hierarchy of classes.
Re:When FP? And When OOP? (2013) by thesz on Tuesday January 29, 2019 @10:43AM (#19027188)
As a person who spent more than couple years writing modeling and simulation software and modeling various systems, I cannot agree at all.

In digital logic circuits simulation you will be forced to compute things transactionally - update all relevant outputs at once, - otherwise you will have interesting and subtle errors to debug.

For single clock domain designs it is best done using Haskell's data type like "data S a = S a (S a)", i.e., infinite lists where each element is a state of a "bus" at the beginning of the clock cycle. The register then can be defined as "register resetState inputs = S resetState inputs", addition as "addS (S x xs) (S y ys) = S (x+y) (addS xs ys)" and so on.

Then you can combine these "S-processors" in many interesting ways and throw Quickcheck at them with LTL specification of behavour (when input satisfies these conditions, then output must satisfy these) and, after investing some more time, even get working Verilog source for entire system.

Re:When FP? And When OOP? (2013) by tabtab on Monday January 28, 2019 @03:30PM (#19020783)
OOP-based modules seem to only handle relatively simple "structures" well. If we consider these structures as "entities", then it's limited to roughly 5 entities of less than a few thousand instantiated objects before it starts turning into a mess. It has a scaling problem. And FP doesn't entirely seem to solve it either.

My feeling is that better database-to-app-language integration is needed. Databases better handle big-picture issues, but integrating them with code is a pain in most frameworks because app world and database world ended up too different for unknown reasons.

It seems there was an anti-database fad(s) starting in the mid-90's that made app languages want to pretend databases didn't exist, to be magically hidden behind an interface. We are paying the price for this head-in-the-sand thinking. We should find a way to embrace databases rather than try to wrap them away, because that failed.

Re:When FP? And When OOP? (2013) by geezerjay on Tuesday January 29, 2019 @04:03AM (#19024875)
> It seems there was an anti-database fad(s) starting in the mid-90's that made app languages want to pretend databases didn't exist, to be magically hidden behind an interface.

This assertion entirely misses the point.

In most applications which employ a db to handle persistence there is a need to map relational data to objects. Either you reinvent the wheel each time you need to handle data to/from the persistence layer, by rolling your own ORM infrastructure, or you reuse a component that is far less bug-prone and efficient and far easier to maintain and update. Framework developers recognized the need to free developers from wasting time writing bug-ridden and ineffixient boilerplate code, and thus started offering their own ORM frameworks. Real-world developers who felt the pain of having to roll their own ORM components or recognized the value of using those tools, whether in reducing bug count, increased performance, lower time to market, and lower maintenance needs.

Your comment sounds an awful lot like the comments from decades ago where random geeybeards whined that these new fangled compilers and interpreters want to pretend machine code doesn't exist, and how hand-written assembly is the right tool for the job.

Re:When FP? And When OOP? (2013) by tabtab on Tuesday January 29, 2019 @12:53PM (#19028328)
Re: or you reuse a component that is far less bug-prone and efficient and far easier to maintain and update.

I'm still looking for that. Where is it? I've yet to find a good ORM. They end up being dark grey arcane boxes that require dedicated specialists to troubleshoot and tame. It appears objects and RDBMS just don't mix well: https://en.wikipedia.org/wiki/Object-relational_impedance_mi...

I suggest we look at using more relational concepts on the application side instead of trying to translate back and forth between paradigms, spending too much grey matter and code on translating and converting back and forth.

Oracle Forms shows some possibilities. It requires about 1/5 the source code of OOP frameworks for similar CRUD applications. There are clunky aspects to Oracle Forms, I admit, but I believe the good stuff can be borrowed without also being forced to take the bad stuff. It's a starting point to app/DB integration exploration.

Re:When FP? And When OOP? (2013) by jghn on Monday January 28, 2019 @01:48PM (#19019975)
Which tracks with the lineage. SIMULA has an argument for first OOP language
Re:When FP? And When OOP? (2013) by rkangel on Monday January 28, 2019 @05:41AM (#19016438)
ADTs are common in statically typed FP languages (and others you mentioned like Rust and Kotlin). I have been working with Elixir a lot, and being dynamically typed the concept isn't appropriate.

Pattern matching is the concept that they all have in common, that I miss when it isn't available. You don't have the 'exhaustiveness' check in Elixir because it's not possible in a dynamic language (dialyzer aside), but it's still a very powerful tool.

Re:When FP? And When OOP? (2013) by chriswarbo on Monday January 28, 2019 @06:49AM (#19016733)
I write Scheme code as if it had ADTs, e.g.

    (match payment
      [('invoice address     ) (foo address     )]
      [('card    card-details) (bar card-details)])
As you say, there's no exhaustiveness check; plus the language won't guarantee that the product types are adhered to. For example, someone might give us `('invoice card-details)` and the language wouldn't complain.

Racket's contract system can help with this; although it's very slow.

Re:When FP? And When OOP? (2013) by YorkshireSeason on Monday January 28, 2019 @08:00AM (#19017093)
Use Scala -- first class support for objects and ADTs.
Re:When FP? And When OOP? (2013) by mcintyre1994 on Monday January 28, 2019 @09:01AM (#19017434)
But not for that syntax unfortunately, unless I'm missing it? I use sealed traits and case classes to do the same - the union syntax would be much nicer.
Re:When FP? And When OOP? (2013) by virtualwhys on Monday January 28, 2019 @09:30AM (#19017624)
Coming in Dotty/Scala 3.0, along with a slew of other improvements to the language.

Combine that with the proposed new enum syntax and existing sealed trait plus case class/object boilerplate will be gone. Looks pretty great, but we'll have to wait until next year sometime to use it in production.

Re:When FP? And When OOP? (2013) by labourcurious on Monday January 28, 2019 @07:42AM (#19016987)
In Java, you can use https://github.com/derive4j/derive4j to achieve this.
Re:When FP? And When OOP? (2013) by tabtab on Monday January 28, 2019 @03:26PM (#19020761)
You seem to be assuming that modelling payments as sub-types is appropriate. It may not be, or not stay that way over time. Often one needs a buffet of independent or semi-independent features to be flexible. Hierarchy-centric ontologies, such as "types", are limiting.

Yes, I know types don't "have to" be hierarchical, but they are usually messy to manage as non-hierarchies.

Re:When FP? And When OOP? (2013) by dunefox on Monday January 28, 2019 @05:47AM (#19016461)
AFAIK Kotlin only supports exhaustiveness if the result is assigned to a variable.
Re:When FP? And When OOP? (2013) by TheCondor on Monday January 28, 2019 @09:49AM (#19017769)
I don’t disagree with your sentiment exactly but it seems like an apples to oranges comparison. I would think adding cash would be as simple as something like: Class cash implements PaymentTypes { ..

And then the compiler will tell you all the stuff you have to do to implement a payment type. It might be a little or it might be a lot. Your ADT example also might be a little work or it might be a lot. I know some, or many, “frameworks” in the oop world might have that abstracted out n degrees for some rare or imaginary problem where you have to implement the abstractpaymetypefactoryfactory and all it’s constituents... to be fair that would ha e the ability to solve that rare or imaginary other problem though.

Re:When FP? And When OOP? (2013) by alkonaut on Monday January 28, 2019 @12:35PM (#19019308)
Yes, Obviously you can describe the same thing in any reasonable language, the difference is mainly how concise it is, and how simple it is to overview.

Traditional OO focused on extensibility over everything else. I think the idea of openness to extension is way oversold. The idea of a closed and simple to overview enumeration is much more common in my experience (after 25 years of OO...) and it’s sadly hell to create in C# compared to any language with ADTs.