Sunday 16 July 2017

Rich in paradigms

"You can have more!" - this slogan can be find in many tv commercials. This thesis is quite dangerous in IT world because you not necessary want to have more lines of code which may rise probability of bug (although there are stories of some companies having number of code lines in KPIs!) but on the other hand maybe you can benefit by having/knowing more approaches to solve given problem?

Our brains are Overloaded and maybe that's why simple explanations are so pleasant for us. I remember there was a time when I was actually jealous that .NET developers have only one framework to learn when in Java you would feel frustrated knowing you don't understand N available tools because to "be pragmatic" you would have to justify why you chose one approach over another. Yet having rich choice between tool you had only one choice in solution domain - „everything is an object”.

This approach started loosing popularity in recent years When I had a chance to teach scala to both C# and Java developers – first group understood functions quite naturally and even monads were something familiar to them - „it's like LINQ, it's like LINQ”. And Java? "Java made functions popular by not having them". When in 2014 we posted news about Java8 on our JUG group – well, C# group invited us to their meeting to see what will be in Java in 5-10 years.

By having richer language C# devs started learning new paradigms earlier than standard Java dev but of course there is other side – the less complicated language is then you have less power but also maybe less chances to make exotic mistakes. So the question is can we "have more" good things and "have less" problems at the same time?

Golden hammer

On the picture above you can see programming paradigms family – there are several different programming paradigms but we usually focusing on only one of them. Why?

I observed that when programmers are discussing about different approaches to code problems they often concentrate on some particular differences which only in a very specific situation shows „superiority” of their favorite solution. So (now we entering "metaphor mode" ) for example when someone claims that fish is better than bird because bird reveals many disadvantages 10 meters under water – then second person will give counter argument that fish is unable to catch food on trees. Yet – both fish and bird were adapted adapted to specific "context" by evolution.

Can similar adaptation occur on more conceptual level? For example we have paradigm with limitation „an object has to have a mutable state” and paradigm two - „code has to preserve referential transparency” (RT in short means – function calls need to be independent from surrounding state – this improves code readability a lot!)

Similarly to bird and fish it is easy to find examples when those conceptual limitations brings more problems than solutions. But again like with the "animal metaphor" – those approaches evolved in a specific circumstances.

And limitation understood as specialization can bring a lot of good things. Each year we organize Global Day Of Code Retreat where each participant is forbidden to use some specific constructions they are used to in everyday work like „for -> inside for -> inside for ” etc. which leads them to better Object Oriented Design. OOD – because most people choose Java as heir primary language.

There is CodeRetrat and there is professional life. In life given language bring limitations like :

  • Java - for last 20 years Java did Code Retreat with limitation „everything is an object” - what for 5% of programmers whom I know means good practices of OOP design – but for the rest – dozens of fors closed in „SomethingManager”.
  • Haskell – you are limited to good (or bad) Functional Programming design.

Maybe first language wins in one specific environments with specific problems and another can solve different class of problems? What if you have both classes of problem in you project?

To support this thesis lets look at a very interesting quote from a very nice book : Concepts, Techniques, and Models of Computer Programming

Adding a concept to a computation model introduces new forms of expression, making some programs simpler, but it also makes reasoning about programs harder. For example, by adding explicit state (mutable variables) to a functional programming model we can express the full range of object-oriented programming techniques. However, reasoning about object-oriented programs is harder than reasoning about functional programs"

Also Effective Java – more than 10 year old book now – promotes immutable structures as easier to operate and understand (Still whenever performance is at stake you will most likely end up with mutable structures.)

Everything is an object?

Lets start by looking at one interesting example of limitation which is only limitation in thinking. According to my current knowledge OOP focuses on solving problems by managing changes of mutable state. State is encapsulated in an object which is a noun . Noun from time to time is called Invoice, Customer or Money which makes easier to convince business people to this approach.

However OOP has no monopoly for nouns. In FP we can easily create new types.

data Customer= Customer {firstName::String, lastName::String} deriving (Show)

let c=Customer {firstName="Stefan", lastName:: "Zakupowy"}

Prelude > c
Customer {firstName = "Stefan", lastName = "Zakupowy"}

Polymorphism

There was a time when word „polymorphism” triggered „Pavlow reaction” in my brain : „polymorphism <←--> Inheritance”. How surprised I was when I discovered there is a lot more!

And for example this "ad hoc polymorphism" - it is something very exotic in java world. Lets assume we have some tomatoes, very clean and very pure domain tomatoes.

case class Tomato(weight:Int)

val tomatoes=Seq(Tomato(1),Tomato(4),Tomato(7))

How quickly calculate sum of all tomatoes? You can add Numeric nature to tomato ad hoc and then just use them as numbers :

implicit val numericNatureOfTomato=new Numeric[Tomato]{
  override def plus(x: Tomato, y: Tomato): Tomato =  Tomato(x.weight+y.weight)
  override def fromInt(x: Int): Tomato = Tomato(x)
  override def toInt(x: Tomato): Int = x.weight
  override def toDouble(x: Tomato): Double = ???
  override def toFloat(x: Tomato): Float = ???
  override def negate(x: Tomato): Tomato = ???
  override def toLong(x: Tomato): Long = ???
  override def times(x: Tomato, y: Tomato): Tomato = ???
  override def minus(x: Tomato, y: Tomato): Tomato = ???
  override def compare(x: Tomato, y: Tomato): Int = ???
}

tomatoes.sum  
//res0: Tomato = Tomato(12)
//def sum[B >: A](implicit num: Numeric[B]): B

In Java you would have to implement specific interface and add it to Tomato upfront. Then by extending/implementing you would add Numeric nature to you class. Scala implementation above may look like Strategy pattern at first - and here I can suggest to learn Haskell just for educational purposes to better understand those mechanisms.

And when we mentioned implementing/extending and inheritance overall - technically it is just a realization of more general mechanim called Single Dispatch and when you understand general context then maybe it will be easier to spot advantages and disadvantages of this mechanism - in contrast to popular example in OOP book - "Cat and Dog extends Animal"

Information Hiding

Usually when we think OOP we think Encapsulation (Pavlov again?) - and to be clear it is very good practice - but let's stop and think for a moment (again) to understand where it came from and if it is only characteristic for OOP or maybe it is more general and we don't need OOP to have it?

And again encapsulation is an example of more general concept : Information hiding which leads to better modularisation of source code. Haskell is far from OOP but it has modules and is able to hide information in those modules. Then you have an alternate mechanisms to "object method" for accessing those private data. One of such mechanisms which is considered mainly to be FP mechanism is Pattern Matching

First of all be aware that Data not always have to hide something - an example can be Scala's case class which only represent some record. Still Scala because of it FP+OOP nature allows you to connects Pattern Matching with object encapsulation very nicely.

So having a module to play card game :

trait Game[Card]{
  class Deck (private val cards:List[Cart]){
    def takeCard : (Card,Deck) = (cards.head,Deck(cards.tail:_*))
  }

  object Deck{
    def apply(cards: Card*)= new Deck(cards.toList)
    def unapply(deck: Deck):Option[Card] = Some(deck.cards.head)
  }
}

Structure used to store cards (List) is invisible from the outside. Next step is to implement given game with simple String representation.

object StringsGame extends Game[String]{
  def take(d:Deck)= d match {
    case Deck("ace") => "I have ace"
    case _ => "something else :("
  }
}

And now we can play

import StringsGame._
val deck1=Deck("ace","king","ten")
val (_,deck2)=deck.takecard

play(deck1) //res2: String = "I have ace"
play(deck2) //res3: String = "something else :("

Abstractions

Talking about abstractions can be very abstract itself (here it is a very good presentation about this topic Constraints Liberate, Liberties Constrain — Runar Bjarnason). To not "fly" to far from the topic lets focus on a good practice frequently used in Java -> programming towards abstract interface to postpone reveal of details and preserve freedom of choice. So For example very often Collection or List types are used in declaration to not reveal that we have LinkedList

Yet sometimes abstractions are not that obvious and to spot them you need to join known facts in a new way. Maybe there is something similar between Optional and CompleatableFuture when at first sight those mechanisms seems to be completely separated and designed for completely different things? It maybe the hardest thing to change someones "stabilized" opinions and views on what is and what is not good practice in given context to learn new approaches to a problem.

When Good is Bad?

Till now we mainly discussed about good sides of using multiple paradigms but are there some downsides? Sometimes borrowing a concept from one paradigm and using it in different context creates a disaster and then people tends to blame only a concept forgetting about the context.

For example Utils class in OOP context very often signalizes design smell because where we expect separated encapsulated states we came across at a "bag of loosely connected methods".

On the other hand there is org.apache.commons.lang.StringUtils which is... indeed... very convenient to use even in "hardcore" OOP context. What went good there? IN OOP there is a metric called LCOM4 which measure object cohesion by checking if there are some independent states within object state. Yet we can not use it for StringUtils - StringUtils doesn't have any state. StringUtils aren't also coupled with any external state and this is crucial. Custom "Utils" very often are procedures which operates on external state - StringUtils are independant. A pure function (without function keyword) in the middle of Object Oriented Paradigm.

When this difference is not understood correctly concept of "stateless class with static methods/functions" is always code smell

Hidden Paradigm

There is one special "paradigm" which is not well described in books about good practices - "paradigm" of writting f***ing unreadable code. It is a complex social phenomenon in which participate whole range of people from corporate ladder. This paradigm is like a logical gate in front of other paradigms. If it is present then we can not talk about correct OOP or correct FP approach because we just have "big ball of mud", "spaghetti" or just "f***ing bad code". Unless you remove this "bad paradigm" - discussion about other paradigms is just a science fiction.

Summary

Imagine that you have two paradigms of movement : walking and running. There can be endless discussion which is better : "When you are running you have a chance to flee from angry dog, - hej! but if you walk silently then maybe dog won't notice you". This example is of course quite stupid because "moving" is a very intuitive "thing" for us and through using it everyday we gain intuition about the context.

Programming is not that intuitive for us and we tend to judge different approaches to quickly. Very often we learn one approach with our first technology ecosystem and then we tend to reject any other. Maybe it is better to first learn in depth how to "walk" and "run" and only then compare those two way of movement? This is called "being an engineer" - to know when to use given tool. (You can of course earn w ton of money by being so called Technology Evangelist and promoting only one technology)

In JVM world for more than a decade there was only one "politically correct" paradigm so maybe to "equalize" concepts people from other paradigms have to be more aggressive than it is needed? I hope dear reader that through this article I was able to show you that different paradigm can enrich each other. Or maybe there is just "one" paradigm which only parts we can see? There is a good quote from one smart book about programming which I think matches perfectly thesis of this text and show how different approaches are connected - "(...)A function with internal memory is usually called an object(...)"

No comments:

Post a Comment