OO or FP easier for beginners

Yep. It’s more like a syntactic sugar and an opt-in convenience at best.

The one and true thing Golang and Rust need methods for is their breed of duck typing which works pretty well in both. (Especially Rust traits and Golang’s invisible “if you implement these exact two methods you are now implementing contract / interface X” policy).

Yeah, you are totally right, I totally forgot about that. Not the biggest fan the way they are implemented in golang, I prefer much more the elixir approach to include them in a module, but one again concept of module in golang is a totally different thing.

They get the job done well (I don’t like them as much as Elixir’s or Rust’s ways to do stuff either). Which is more than what can be said about Java. :003:

1 Like

Testing all the possible interactions, timings and exceptions that can occur in a parallel programing model where race conditions and deadlocks are possible is exceedingly difficult and in fact impossible.

That said I do don’t put Rust in the same camp as traditional OO languages, it is much closer to what we have in Elixir with modules, functions and structs. Rust also adopted an object ownership model for concurrency as well. Rust has made great strides as a safe systems programming language, unlike traddional OO languages it didn’t adopt inheritance, but does have ability for code reuse with analogues to what we have with Elixir behaviors.

I think Rust actually goes extremely well with the BEAM because it took ownership of state seriously.

Golang is also not a traditional OO language, so it also doesn’t really count. It is also not immune to race conditions despite the ability to enable detection of some race conditions.

Isn’t it interesting that all the real advances are coming from non traditional OO languages?

In respect of options handling in HTTPoison / hackney, that is just sloppy code and a lesson in poor defaults handling. Even with types this would still be a poor state of affairs and unfriendly developer and insecure experience. It is almost as bad as throwing away secure defaults just because the domain didn’t end in “.com” or it just happens to April fools day, really it’s inexcusable, but that’s not the only issues with those libraries.

So with a hispter type system if you don’t specify all options would you expect to get a compiler error?

Would that be a desirable outcome if you don’t want to specify every option always?

I’m playing devils advocate here, be careful what you wish for, the next release of a library may add a new option and now your code breaks. Sure a library may deprecate or remove an option but that is now the responsibility of the library author to detect as they have gone and broken the established interface.

I don’t see how types really help with options as they are mostly optional, aside from perhaps catching a dumb typo. There are options libraries like NimbleOptions that do validation including checks for unknown options being passed, I suggest using them, as we don’t need a type system for that.

I want to be clear that I am not against types, I actually feel that elxir and Erlang go beyond types with pattern matching on both shapes AND values. We have value semantics and we can express our expectations.

I guess I characterise the rest of your response is more about bad practice and not having discipline writing code. No language will protect you from badly structured code, bad idioms and poor structure. Give a simple pen with a single function to a 3 year old and see what creative mess they can make with it.

Refactoring is never a big concern for me, and often a symptom of not having a sound structural methodology to begin with.

I like the OTP book, clean/pure functional cores that are easily tested, separate from the genserver interfaces and boundary modules where failures can happen. I find the CRC (construct, reduce, convert) pattern to result in clear understandable code and the use of context modules tend to provide sufficient insulation from lower level implementation changes.

If a context needs to be refactored even this can be done preserving the existing inteface through delegations until such time you have fully migrated code to a newer interface. It’s pretty easy to find where a module is used or imported so I don’t see a difficulty in determining what needs to be refactored.

Hear! Hear! That’s pretty much the crux of it.

There is a key part of Gary Burnhardt’s Ideology talk where he talks about how developers will often never ask “Well if you don’t do things my way, how do you do them?” or more to the point, “I see you do things in a way that didn’t work for me, how do you make it work for you?”

On the flip side, it’s understandable that for the time-being some people around here might get extra defensive as a pretty massive change might be coming. But generally, it would be great if we didn’t need to explain ourselves.

But having said that, dynamic typing works for me because I like following the strong conventions that are needed to make it work. Explicit, consistent naming (manufacturer, not man or mnfctr or m) and writing functions in a way where the return type is obvious are things I’ve learned from trying to write clear dynamic code. I’m also a big proponent of pair-programming. Speaking of juniors and “eliminating entire classes of problems,” pair-programming eliminates the entire problem of letting juniors loose to heck things up! It also eliminates the problem of letting overly “creative” seniors loose to heck things up :sweat_smile: If you are at a company that doesn’t facilitate this, or you don’t like pair programming or want the freedom to write shorter names, than ya, static typing is going to be a big help. I also particularly like Erlang’s flavour of dynamic typing, but that has been mentioned many times.

@D4no0 I’ve realized that there is a big difference between OO and functional type systems but is also something I’ve never been able to articulate so thanks, that is a useful summary!

EDIT: Though I’ll sound like a broken record, I’ll reiterate (from other threads) that I combine all those practices with top down TDD and casting unknown data into known types as soon as it enters the system.

Ok, I have to get to work! :grimacing:

3 Likes

It’s handled quite well. You can specify only 2-3 fields of a struct and let the remaining 30+ be defaults:

Much beyond that: interactions between options and how the defaults play into it. As mentioned in this (or other? can’t remember now) thread I had the misfortune of working with libraries and other people’s code where a library silently reverts config to defaults because you didn’t specify something well enough for it.

NimbleOptions is fantastic and I wish every library that needs config used it. That being said, I already addressed this when I said the following:

And yes I agree we hardly need static types in Elixir because of pattern-matching and guards. But there are blind spots in the community and I find myself wishing for stronger checks there (again, library configurations).

That’s kind of moving the goalposts maybe, but I agree with your premise.

Finally, that’s a bit of a weird thing to say, refactoring is 100% inevitable. Hope you are not making the argument that we have to dream up 100% of the app before we hear the 50+ “I changed my mind” complaints from customers.

1 Like

Ah, here’s where I gave an example with Rust btw: Types 'n' Testing - #8 by dimitarvp

Of course there is refactoring and I am hardly able to plan everything perfectly, it is just not a concern in practice when I have put in place sound structural relationships and abstractions within the code base.

The resulting refactoring tends to be very manageable.

Things that can bite are changes to global aspects like logging and instrumentation but even these are still approachable with savvy search and replace.

My point is that I am more about focusing on the most important factors for maintainability, kind of like if we look after the cents, the dollars take care of themselves.

Even with a type system, no doubt laziness will ensue with abuse of any().

I’m all for “type” inferencing but actually I’m more for value inferencing and letting the compiler work out what the value constraints actually are and avoid sprinkling types where possible.

I think many of the opinions(generally speaking) are way too biased from the experiences of people that already know how to code. And for most of us when we started, FP wasn’t nearly as widespread as it is today so chances are we all started with OOP or imperative programming in general.

My brother is a high school teacher on a technical school. Some of the topics he teaches include automation of industrial systems(mostly PLCs, and recently stuff like Arduino). I taught him a fair bit of Elixir, C++ and JavaScript for various things he needed to do.

Of those three languages, most of Elixir was the easiest to learn, it’s just functions that take data and return data, there’s no “global variables” that change under your feet or anything. It was very straightforward. The idea of a “list” and “mapping over it” made sense, it was easy to explain at a high level.

Recursion was a bit weird to explain but he had some parallels to draw from so he got it quite quickly, though we didn’t get into body vs tail recursion.

We didn’t get into message passing in the language, we did discuss the concept and it made sense to him but we never used it in practice.

JS and C++ were a different story, he knew what functions are and that data can have different shapes, but he stumbled mostly on:

  • Keeping state: he always lost track of the program’s global state(they’re normally global variables in arduino projects); he would often declare some step at the top of the files and then forget about them because the functions he was writing didn’t have that as arguments(conversely I think keeping program state in elixir would have been confusing but we didn’t get to it)
  • His students always struggled to understand how variables work(this would probably have been the same for Elixir)
  • What a class and an instance are, and functions vs methods, required lots of explanations. And lots of very basic code requires you up-front to interact with them.
  • for loops required me to explain them countless times: the syntax is usually weird, and the process is always so manual that he(and his students) got lost pretty much every time they needed them. Most bugs were caused by this
  • In C++ having to declare the type of each variable caused paralysis most of the time(should I use int? long? why signed vs unsigned? I don’t know how many array items I need)

There’s lots of stuff we take for granted and consider easy when we already went over them, but for complete novices the experiences are too different and wildly influenced by previous learnings.

Maintainability and refactoring are the least of the concerns at those stages imho, as is the notion of what’s “pure” or “impure” or what “effects” and “side effects” are. By the time these become relevant you’re no longer a complete novice, but rather someone that knows their way around programming things and wants to commit to the next levels.

3 Likes

I remember the first time someone mentioned “side effects” with respect to some ruby code I had written and I was completely befuddled. Definitely not something that seemed relevant until much later in my journey.

1 Like

Right, yeah, that was my point re LSP and ctags, jumping through files to definitions has always (well, for 30 years) been available in reasonable forms. My point re implicitness is just that this “finding where a method/function/identifier is defined” is not a problem unique to OO, it has existed forever in virtually all languages.

1 Like