Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

FP Lite

Less filling?


The Imperative/Object vs. Functional/Declarative paradigm has confused many a beginning developer; even the seasoned developer writing this article. Here I attempt to shed some light.

Why FP? For starters, controlling object hierarchies and keeping track of object state is one of the most difficult challenges of OO design. Additionally, we live in a world of interactions, not models in a fixed state. FP aims to simplify those interactions by reducing the scope of what could possibly go wrong. FP aims to create fixed, 100%-predictable-at-runtime operations vs. the polymorphism and dynamic composition so often seen in OO.



Huh? 😕


Many an FP project was begun as a pure NodeJS project, only to fall back on TypeScript when the need for type-checking and compilation became too obvious to ignore. Why is this?

There seems to be a push and pull between the flexibility that JS offers (the kind of "super dynamism" inherent in the JS prototyping model) vs. the adherence to SOLID principals that typed modeling with pre-compilation provides. When implemented reasonably and practically, FP can greatly simplify a project or at least simplify some of its lambda-izeable code.


The Virtues of FP

  • FP's versatility lies in its ability to abbreviate logical statements with lambda calculus, thereby shrinking the solution space and reducing cognitive load for the developer (for enthusiastic students of Calculus theorems, at least 😃)
  • Also => x = > x.Happiness


The Virtues of OOP

  • Since the release of Smalltalk, has seen widely successful iterations in Java and C#
  • Its footprint is vast and its legacy rich (C# 12, .NET 6, Java 18)
  • OO development is still the best way humans have to model entities and complex hierarchies; FP does not magically solve or remove the OO state-tracking problem (and actually makes debugging less transparent in many cases), it merely pushes it to another corner of the development space (the function implementations).

When Alan Kay coined the term “Object Oriented Programming” in the 1960s he had a background in biology and was attempting to make computer programs communicate the same way living cells do. Kay’s big idea was to have independent programs (cells) communicate by sending messages to each other. The state of the independent programs would never be shared with the outside world (encapsulation). Said Kay years later after OO had achieved market dominance, "I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is messaging.".


FP simplified: function interaction for behavior vs. object interaction for behavior.


Indeed.

In my experience with it, FP yielded successful programs, but never did I feel that I had intimate knowledge of how things were working outside of the modules I implemented or modified because the code was not inherently readable- to me, that is. When things go wrong, that can be a problem no matter how predictable the (wrong) results are. Certainly, with time and exposure, an OO developer can learn to reason about FP code equally if not more effectively than OO code; a lot of this debate is, at its core, a preference of style and mental model and has no right/wrong answer.

Oftentimes, the best decision is to use a little bit of both OO and FP. With the introduction of lambda statements in C#, .NET became a veritable proving ground for any software developers who knew they could greatly simplify .NET code by flattening all those convoluted and bug-prone foreach and for loops into simple lambda maps. Making certain properties readonly in an OO project will shine a (useful) bright light on just how much your code has spiraled out of control... Know?

For those who claim that FP will magically prevent all bugs, don't even try obfuscating the truth:

You cannot run away from or completely shield yourself from complexity.

Not by declaring, "we are an FP team now, as such all behavior must be function interactions, no mutable state anywhere!". That isn't what FP promises. Implemented practically however, FP is a far superior solution for certain use cases and certain development teams (think- very math-oriented software engineers).



In this instance, the case for FP is clear; in .NET we can use FP within OO via Lamba fn()'s which achieves the simpler code on the right


FP Drawbacks and warnings

  • Sometimes not feasible when trying to model application entities and their behaviors
  • The same brevity that some developers love in FP can cause readability issues for others
  • Often the tests for FP become so large that any gain in brevity is kind of negated

OO Drawbacks and warnings

  • Type modeling can lead to an excessive number of types, increasing program size and complexity
  • Bugs related to state mutations as a result of function/object side-effects
  • More problem space for things to go wrong


More than any of the three issues above, the biggest problem I have with functional programming (in the Node ecosystem at least) is that the entire supply chain is open source and that can lead to neglected, abandoned, sabotaged or broken projects without warning. Love or Hate MS, they make sure (some) bugs are fixed and libraries are maintained.

When a NodeJS package/module that you depend on becomes obsolete or is compromised it forces teams to find an alternative and in the worst cases- entirely rebuild functionality with completely new dependencies.

And related to this, there is a bit too much over-distribution/splintering of source code going on with many (most?) FP Node projects. 20 dependencies in a project, sure. But I've worked on Node projects, that only involved a few functions, which relied on hundreds of NPM package dependencies, many of which were several versions out of date (and couldn't be updated to current, because, well y'know, it's a breaking Node package update! Weeeeee... 😃 (Ç'est la vie nous vivons))


😆


On the flipside, there is something immensely liberating about having not only a completely commercial-free OS like Linux, but also, free IDEs and a worldwide bazaar of NPM packages that fulfill virtually any development requirement that one might ever encounter.

FP style emphasizes ensuring your code has "high signal-to-noise ratio"; when things break, we want to know where it hurts and why, immediately. To paraphrase Kent Beck in his book on unit testing, stack traces of poorly written integration tests and production failures often tell us there is a problem in our application, "but, where oh where?".

If we keep our abstractions tightly wound and limited (and oh boy does FP do that in spades), we will immediately become aware of any issue in our program and know exactly what line or lines of code are responsible.


The "core" of FP is in its ability to do several things at once (parallelism- prime domain for large list processing)


Summary

FP is only going to gain a greater and greater share of the software development world as more and more mathematically inclined developers join the ranks of the IT industry. To be sure: Java and .NET have a lot of great stuff on Maven and Nuget, respectively.


More than anything else, FP is a mindset, or a "mental model" of how program messaging is defined.


You can even collaborate on open-source project in .NET and Java and even C++ in in this day and age of 2022. In fact, MS Documentation and a large portion of the MS code library is now open source.

However, no open-source communities are as active as the ones you will find for FP (Lisp, Go, NodeJS, Haskell, Clojure, F#). Whatever Lisp or Haskell may lack in worldwide development footprint they make up for in developer enthusiasm and evangelizing the FP mindset and how it can lead to more concise and more "easier to test and reason about" software application code.

More than anything else, FP is a mindset, or a "mental model" of how program messaging is defined.

More specifically, it is the mindset of passing around functions-as-arguments-to-other-functions (like recursion, but for everything in the program) instead of object composition, state tracking and function arguments. This not only eliminates the need for state tracking and therefore much of the problem space, but it also opens up the application code to mathematical proof modeling of the application behavior requirements- a huge productivity gain if your development team is a heavily math-oriented bunch.


OO vs. FP


Lisp is short for "list processor". Naturally, programs involving list manipulation and uniform message processing are a very good candidate for FP code which is excellent at doing very specific tasks that involve inputs and outputs and not much else- at huge scale.

Like most software development trends, FP is not new (Lisp was developed at MIT in the late 1950s), but it is experiencing a renaissance thanks to developers rediscovering and extolling its virtues with the larger (and largely OO since 1990s) software development community.

A little bit of FP can go a long way and can clear away complexity in code like so much brushfire. But don't go using it to look for problems in existing OO code that don't exist.



FP Quotes

"You can use OO and FP at different granularity. Use OO modeling to find the right places in your application to put boundaries. Use FP techniques within those boundaries" -OOP vs FP

"OOP is not natural for the human brain, our thought process is centered around “doing” things — go for a walk, talk to a friend, eat pizza. Our brains have evolved to do things, not to organize the world into complex hierarchies of abstract objects." -FP essay

OOP does not have enough constraints in place that would prevent bad programmers from doing too much damage. " -Ilya Suzdalnitzski 

"Encapsulation is the trojan horse of OOP. It is actually a glorified global mutable state" -OO, the trillion dollar disaster



References:

https://betterprogramming.pub/object-oriented-programming-the-trillion-dollar-disaster-92a4b666c7c7

https://dev.to/bhaveshdaswani93/oop-vs-fp-with-javascript-39jf

https://betterprogramming.pub/object-oriented-programming-the-trillion-dollar-disaster-92a4b666c7c7

https://www.lihaoyi.com/post/WhatsFunctionalProgrammingAllAbout.html#the-core-of-functional-programming

A Timeless Book (by Jerry Fitzpatrick)

TL; DR;

This book gets right to the point of the common aspects required for building great software and is too valuable for any software development professional not to read; and it contains just 172 pages! 😃


Read this book.


Plan Before Implementing

Proper and properly named abstractions to match problem domain and purpose. Thoroughly documented and well-understood behavioral interactions among all accessible components and at minimum a solid high level understanding of all dependencies; know when not to re-invent the wheel and instead utilize a proven 3rd party or open source tool.


Keep it Small

YAGNI ("You ain't gonna need it") is a very real thing. Development teams should focus on working prototypes that can be ironed out for production vs. forever "ideal" implementations that never make it to an actual user's screen.


Write Clearly

Treat a rough draft as a rough draft by encouraging code reviews and frequent minor refactoring to achieve code clarity for the next developers who will inevitably read (and need to understand in order to change or extend) your logic in the future.


Prevent Bugs

Meticulously control scope and member access and understand all ramifications of behavioral code points within the application. Every area of variation/mutation/state change should be covered by tests that are both named and implemented to clearly demonstrate the function/class/behavior being tested.

All tests are important for confidence/assurance in ongoing development but TDD (writing tests before the implementation a la Kent Beck) is not a replacement for good, cohesive design. That being said, I think the TDD backwards approach can help start great design discussion among developers when the design is somewhat unclear (tests, particularly broken tests will expose the innards that most warrant discussing).


Make the Program Robust

Utilize agreed-upon code formatting and pattern implementation standards and non-antagonistic code reviews to ensure these standards are being followed.

Utilize information hiding and useful categorization and encapsulation that extends but does not lend itself to potential client issues.

Do not try/catch everything and log everything unless necessary for compliance or a particuliar initiative being monitored. Make Exceptions very visible (but exit the program gracefully if the exception is not "handleable") and integrate tests to prevent the same (preventable) issues from recurring. Instead use try/finally and let exceptions bubble up to the surface application code.

Exceptions that are allowed to be handled and logged hide problems in the code that need immediate attention.

Use solid CI/CD and automated testing that tests for easily definable application behaviors.


Prevent Excess Coupling

Favor atomic initialization: Initialize everything all at once versus incremental composition.

Discourage unnecessary extensibility points and instead expose what needs to be exposed with as little information sharing as possible.

Strive for immutable members wherever possible.

Cohesive, self-evident abstractions are of utmost importance.


SUMMARY

The concepts so succinctly covered in Timeless Laws of Software Development apply to all types of code (imperative or declarative) and all languages.

While I've read many great software books and all have helped me become a better developer, no text has struck me as so plainly obvious and concise at explaining good software design concepts in such an easy-to-grok manner.

For a worthwhile read, check out Timeless Laws of Software Development by Jerry Fitzpatrick

Java from .NET and .NET from Java

Java or .NET? Why not both (when it is the only viable path)?

jni4net is a proven interop library for Java and .NET. Two brief examples developed by jni4net below merely require that you to specify the jni4net dependency in the (Visual Studio or Eclipse) project.

Calling Java from .NET
 using java.io;  
 using java.lang;  
 using java.util;  
 using net.sf.jni4net;  
 using net.sf.jni4net.adaptors;  
 namespace helloWorldFromCLR  
 {  
   public class Program  
   {  
     private static void Main()  
     {  
       // create bridge, with default setup  
       // it will lookup jni4net.j.jar next to jni4net.n.dll  
       Bridge.CreateJVM(new BridgeSetup(){Verbose=true});  
       // here you go!  
       java.lang.System.@out.println("Hello Java world!");  
       // OK, simple hello is boring, let's play with Java properties  
       // they are Hashtable realy  
       Properties javaSystemProperties = java.lang.System.getProperties();  
       // let's enumerate all keys.   
       // We use Adapt helper to convert enumeration from java o .NET  
       foreach (java.lang.String key in Adapt.Enumeration(javaSystemProperties.keys()))  
       {  
         java.lang.System.@out.print(key);  
         // this is automatic conversion of CLR string to java.lang.String  
         java.lang.System.@out.print(" : ");  
         // we use the hashtable  
         Object value = javaSystemProperties.get(key);  
         // and this is CLR ToString() redirected to Java toString() method  
         string valueToString = value.ToString();  
         java.lang.System.@out.println(valueToString);  
       }  
       // Java output is really Stream  
       PrintStream stream = java.lang.System.@out;  
       // it implements java.io.Flushable interface  
       Flushable flushable = stream;  
       flushable.flush();  
     }  
   }  
 }  


Calling .NET from Java
 import net.sf.jni4net.Bridge;  
 import java.io.IOException;  
 import java.lang.String;  
 import system.*;  
 import system.Object;  
 import system.io.TextWriter;  
 import system.collections.IDictionary;  
 import system.collections.IEnumerator;  

 public class Program {  
      public static void main(String[] args) throws IOException {  
           // create bridge, with default setup  
           // it will lookup jni4net.n.dll next to jni4net.j.jar   
           Bridge.setVerbose(true);  
           Bridge.init();  
           // here you go!  
           Console.WriteLine("Hello .NET world!\n");  
           // OK, simple hello is boring, let's play with System.Environment  
           // they are Hashtable realy  
           final IDictionary variables = system.Environment.GetEnvironmentVariables();  
           // let's enumerate all keys  
           final IEnumerator keys = variables.getKeys().GetEnumerator();  
           while (keys.MoveNext()) {  
                // there hash table is not generic and returns system.Object  
                // but we know is should be system.String, so we could cast  
                final system.String key = (system.String) keys.getCurrent();  
                Console.Write(key);  
                // this is automatic conversion of JVM string to system.String  
                Console.Write(" : ");  
                // we use the hashtable  
                Object value = variables.getItem(key);  
                // and this is JVM toString() redirected to CLR ToString() method  
                String valueToString = value.toString();  
                Console.WriteLine(valueToString);  
           }  
           // Console output is really TextWriter on stream  
           final TextWriter writer = Console.getOut();  
           writer.Flush();  
      }  
 }  
(verbose commenting by Pavel Savara, a jni4net contributor)

References:

http://zamboch.blogspot.com/2009/10/how-calling-from-net-to-java-works.html

http://zamboch.blogspot.com/2009/11/how-calling-from-java-to-net-works-in.html

https://github.com/jni4net/jni4net/tree/master/content/samples

Patterns Simpl: Singleton

It is imperative to ensure atomicity (indivisible-ness) for certain "master" objects in your applications.

Singleton ensures the existence of 1 and only 1 object instance (or version/representation) of an application entity- at all times.


Some examples of Singleton:

If your program somehow winds up with 2 globally-scoped objects of a class that handles bank deposits, let's hope our account gets updated by the one with a larger dollar amount!

In Java, there is a single instance of java.lang.Runtime. If there were more than 1 language instance to interface with the Java application environment, you can see how problems would unfold.

In .NET applications, an application .config file serves as a single source of configuration values.

Log Managers should have 1 and only 1 instance, lest our log files become far too complex to sift through when we are tracking down an application issue.




Reference: https://stackoverflow.com/questions/3192095/where-exactly-the-singleton-pattern-is-used-in-real-application