Saturday, August 10, 2013

Object Equality in Scala

Object equality has been confusing developers right since its inception (OOP).
What is object equality? Well! It has different meanings in different contexts. Lets start listing the contexts and scenarios where object equality makes sense

1) "Equality" in terms of object references.
This is the least used equality context and the scenarios include
a) When one wants to check whether two objects references point to the same object or not. If they do, then the output is a true else a false.
           e.g1: val a = new Product(1,2)
                    val b = new Product(3,4)
If referential equality is applied between 'a' & 'b' then the output would be 'false' because                       'a' & 'b' refer two different objects in memory.
Scala language has a special operator which is only used to assess referential equality                             between two objects. The operator is "eq" .
In the above example a eq b returns false as 'a' and 'b' point to two different objects in                             memory.

Lets have another example:
          e.g2: val a = new String("Sandeep") 
                   val b = new String("Sandeep")
                 
Just like the previous example, the expression a eq b returns a false even though the String
constructor has the same string "Sandeep". This is because the 'new' operator creates a new                   String object every time new String() constructor is called.

Consider this example
           e.g3: val a = "Sandeep"
                    val b = "Sandeep"
Any String is immutable is Scala, be it created using the new operator or created using the                       "" operator. The expression a eq b returns a true in this scenario. The reason is that when                       ever a String is created with the "" operator, it is internalized, meaning that the JVM keeps                     track of all the similar strings and stores them in a special location. The size of this location                     can be sent as a JVM argument using -XX:StringTableSize=n , where n is the number of                       bytes you want to allocate to interned strings.

Therefore, whenever the "same" new String "vals" are created using the "" operator, the                        JVM makes sure it points all of the references to the interned Strings. This way the memory                    is optimized.

Coming back to our equality discussion, in this example, since the vals 'a' & 'b' point to the                   same interned String, the expression a eq b returns true.

2) "Equality" in terms of natural equality
Sometimes, or even more often, we run into situations where natural equality makes more                     sense than anything else. The scenarios are comparing two integers, comparing two Strings,                   comparing floats, comparing Double or even Boolean etc. Scala has a special method "=="                  which is used for assessing natural equality of objects in Scala.

e.g1: val a == 1
         val b == 2
The expression a == b evaluates to false
                         
e.g2:  val a = 2
          val b = 2
The expression a ==b here, evaluates to true. This is because the only thing the                                       method "==" does here is check for natural equality.

e.g3:  case class Product(val x:Int, val y:Int)
          val a = new Product(1,2)
          val b = new Product(1,2)
The expression a==b here, evaluates to true. This operator "==" is again making                                     a pure natural check on the objects (The object type, the values in the constructors)

e.g4:  case class Product(val x:Int, val y:Int)
          val a = Product(1,2)
          val b = Product(3,4)
The expression a==b here, evaluates to falseThis operator "==" is again making                                   a pure natural check on the objects (The object type is same, but the values in the                                   constructors are different, therefore returns false)
e,g5:  val a = "Sandeep"
          val b = "Sandeep"
The expression a==b here, evaluates to true (In JAVA it evaluates to False as there is                             no natural comparison happening there. We use either "equals" or                                                         "equalsIgnoreCase" methods for natural equality where Strings are used)

The "==" method is of type 'final' and therefore cannot be overridden. This is                                         designed this way to help developers have a choice when they come across                                             dealing with natural equality. They need not go for other methods to check for simple                             natural equality. The "equals" method in objects can be overridden to change the behavior of the "==". Therefore the "==" and the "equals" method are linked. By overriding "equals" method, the behavior of "==" can be changed. By doing this, we give the "==" method the equality behavior we mean, and which makes more sense to us. We will speak about the "equals" method in the next part below.

3) "Equality" in terms of the state of an object w.r.t the other
This is a more widely used and often confusing concept on object equality.

Every object (in Java & Scala) have default implementations of the method "equals". The default implementation here is nothing but a natural check on the objects (checking for its type and for its values in the constructor). But, we might want to add more to assess equality rather than just checking its type and the values in the constructor. This is where the state of the object is used to assess equality.
An example would clear the confusion here.

e.g1:
class Product(val x:Int, y:Int) {
           override equals(obj:Any):Boolean = obj match {
              case obj:Product => this.x==obj.x && this.y==obj.y
              case _ => false
      }
}
Lets test the class by instantiating some objects
val a = new Product(1,2)
val b = new Product(2,3)

"a equals b" returns a false.


Here in this above example, there is a class by the name "Product" and which takes a constructor with values x & y.  The definition of equality which we have overridden here is that the values of x and y in the callee object are checked for equality with the corresponding values of x & y respectively in the caller object. This  is just a custom definition of equality of objects of type Product which we have come up with, as per our requirement. 


Lets consider another example

e.g2:
import Math._
class Complex(val real:Float, imag:Float) {
         override equals(obj:Any):Boolean = obj match {
             case obj:Product =>sqrt (this.real*this.real + this.imag*this.imag) == sqrt (obj.real*obj.real +                                                  obj.imag*obj.imag)
             case _ => false
     }

}

In the above example we have a class which represents Complex numbers. The constructor takes a real part and an imaginary part as values. The definition of equality we have overridden here is that the resultant of the caller object should be same as the resultant of the callee object. RESULTANT is (Square root of the sum of the squares of the real and imaginary parts)

To test the above class for equality, lets initialize a couple
val a = new Complex(3,4)
val b = new Complex(5,0)


a equals b returns a true (even though the values in the constructors are very different). The resultant of (3,4) is 5 and the resultant of (5,0) is also 5.










Thursday, August 1, 2013

Different Variant of the Custom (typed) Map implementation


trait MyMap[K,V]{
def +(k:K,v:V):MyMap[K,V]
def get(x:K):V
def keys:List[K]
def values:List[V]
def length:Int
}
object MyMap {

def apply[K,V](x:Tuple2[K,V]*): MyMap[K,V] = new MyMapImpl[K,V](x.toList);
class MyMapImpl[K,V](xs:List[Tuple2[K,V]]) extends MyMap[K,V] {

def +(k:K,v:V):MyMap[K,V] = new MyMapImpl[K,V](Tuple2(k,v)::xs)
def get(k:K):V = (xs find (m=>m._1==k)) match {
case Some(x) => x._2
}
def keys:List[K] = xs map (m=>m._1)
def values:List[V] = xs map (m=>m._2)
def length:Int = xs length
}

}

def myTest {

var x = MyMap((1,2),(3,4),(33,56))
println(x  get 33)

x += (35,44) // Adds a new (key, value) pair to the map x
println(x get 35) // Prints 44 (value)

println("Keys-->>"+x.keys.toString) // prints List(1,3,33)
println("Values-->>"+x.values.toString) // prints List(2,4,56,44)
println("Length-->>"+x.length) // prints 4 which is the size of the custom typed map.
}
myTest


Explanation:

My previous version of the custom (typed) map implementation -->>http://dailyscala.blogspot.com/2013/06/create-your-own-typed-map-in-scala.html

The example in the link above uses more imperative style in implementing the map methods but Scala provides many functional ways to implement the + , get, keys and values methods. Lets take them one by one

1)The + method takes two inputs (a,b) where a is the key and b is the value. The implementation takes these two values, converts them into a tuple of the form Tuple2[K,V] and cons (::) it to the original list and returns it. The type of the list returned will be List[Tuple2[K,V]]

2) The "get" method is the most interesting part of the program. This method takes input of type K (here it is "k"), which is the key and returns a value of type V, which is the value. The find operation is called on the current list and the predicate used processes each element of the list (which is a Tuple2), extracts the first element using the ._1 method and compares it with the key "k". The find returns the first element of the list which satisfies the predicate. The returned element will be of the type Option or more specifically Option[Tuple2[K,V]]. Next, this Option value is piped into a pattern match block and the value is matched against Some(x) and the value x is returned as the matching value to the key "k".

3) The "keys" method returns a list of all the keys inside the custom implemented Map. The implementation of this method is as follows=> The map method is applied to the list of tuples. A map applies a predicate to all the elements of the list. The predicates converts the element to a different form.
For e.g: List(1,2,3) map _+1  will return the list List(2,3,4). Here each element of the list is converted to a different element using the predicate "_+1". Similarly, in the custom map, each element of the list (Tuple2) is converted into a type K using the predicate "m=>m._1". Here "m" is the Tuple and m._1 is the first value of the Tuple, which is the "key". The new list of all the converted values is returned. The type of the returned list will be List[K]

4) The "values" method also is similarly written as the "keys" method above.





Monday, June 24, 2013

Stackable modifications in Scala using Traits

abstract class Product {
def get:Double
def put(x:Double)
}
class Computer extends Product {
var price:Double = 0.0
def get = price
def put(x:Double) = price = x
}
class KeyBoard extends Product {
var price:Double = 0.0
def get = price
def put(x:Double) = price = x
}
class PriceProcessor {
var prods:List[Product] = List()
def +(x:Product) = prods::=x

def totalPrice:Double = {
var pri:Double = 0.0
for(prod<-prods) {
pri+=prod.get
}
pri
}
}
trait ApplyDiscount extends Product{
abstract override def put(x:Double) = super.put(x*0.8)
}
trait AddTax extends Product {
abstract override def put(x:Double) = super.put(x - x*0.07)
}
object MixinTraits {

def myTest {

val prod1 = new Computer
prod1.put(1200)
val prod2 = new KeyBoard
prod2.put(80)

val proc1 = new PriceProcessor
proc1+prod1
proc1+prod2

val price1 = proc1.totalPrice
println("PRICE-->"+price1)

val proc2 = new PriceProcessor
val prod3 = new Computer with ApplyDiscount
prod3.put(1200)
val prod4 = new KeyBoard with ApplyDiscount
prod4.put(80)
proc2+prod3
proc2+prod4
val price2 = proc2.totalPrice

println("PRICE WITH DISCOUNT-->"+price2)
val proc3 = new PriceProcessor
val prod5 = new Computer with ApplyDiscount with AddTax
prod5.put(1200)
val prod6 = new KeyBoard with ApplyDiscount with AddTax
prod6.put(80)
proc3+prod5
proc3+prod6
val price3 = proc3.totalPrice

println("PRICE WITH DISCOUNT AND TAX-->"+price3)
}
}
MixinTraits.myTest


//OUTPUT
PRICE-->1280.0
PRICE WITH DISCOUNT-->1024.0
PRICE WITH DISCOUNT AND TAX-->952.32


Concepts:

Mixin Traits: Traits can be used to achieve polymorphism by using it to change the behavior of an object at runtime.
Traits are not just similar to interfaces in java but also have more attributes than interfaces.
Traits can also have concrete methods defined which can be inherited by classes/traits.

eg: abstract class A
   trait T1 extends A
   class B extends A with T1
 
Stackable modifications: A class object can achieve multilevel transformations (or decorations) by mixing it with one or
more traits. The "with" keyword needs to precede each trait the class mixes with. These traits are stacked one
upon the other starting from the right, with the right most being on the top of the stack. The trait get applied
to the class starting from the right and proceeds left ultil all the traits have been used to decorate the class.

As an example:
   abstract class A
   trait T2 extends A
   trait T1 extends A
   class B extends A with T1 with T2
 
   The trait T2 is first applied and then the trait T1 is applied to the class B.
 

Rules to make traits stackable

1) Consider the example:
abstract class A

trait T1 extends A

class B extends A with T1
class C extends A with T1

   The classes B and C extend the abstract class A. Therefore the trait (T1) mixing classes B and C also
   has to extend the abstract class A.

2) The method signature in the trait which is used to change the behaviour/state of the class which it mixes with,
   has to precede with "abstract override"

eg: abstract override def put(x:Double)

3) Mixing a trait with a class can be done in two ways.

a) While creating the class signature:
class A extends T1 //extending a trait

b) While creating the class instance
val val1 = new class A with T1 //mixing a trait


Code explanation:

1) Product is an abstract class with abstract get and put methods.
2) There are two concrete implementations of Product, namely Computer and Keyboard. These
   classes give concrete implementations of get and put methods.
3) The class PriceProcessor calculates the prices of products from a list. The totalPrice method
   iterates through each product from the list and adds the price of it to a variable "pri".
4) the traits ApplyDiscount and AddTax are used to mix with the classes Computer and Keyboard.

Client Code: (object MixingTraits)
//NORMAL CLASS BEHAVIOR
1) prod1 and prod2 are objects of classes Computer and KeyBoard respectively. The put method on these objects are used to set the prices. These objects prod1 and prod2 are added to an instance proc1 of
  the PriceProcessor class using the "+" function. This function adds appends products to a local list
  object prods present in the PriceProcessor class.
2) The totalPrice method on the PriceProcessor object proc1 is called and the total price of a Computer and a Keyboard is calculated.

//MIXING CLASS OBJECTS WITH TRAITS
3) prod3 and prod4 are objects of classes Computer and Keyboard respectively. The interesting thing is
  the mixing of a trait ApplyDiscount to the instances created.
  val prod3 = new Computer with ApplyDiscount
  val prod4 = new KeyBoard with ApplyDiscount
 
  Here the call super.put(x*0.8) delegates the call to the trait/class towards its left.
 
  abstract override def put(x:Double) = super.put(x*0.8)
 
  The discount get applied to the object mixing with this trait.

//STACKING TRAITS
4) prod5 and prod6 are objects that mix with more than one trait namely ApplyDiscount and AddTax.
  At first the AddTax trait is applied and delegated to ApplyDiscount and finally to the class mixing these traits.
 val prod5 = new Computer with ApplyDiscount with AddTax
 val prod6 = new KeyBoard with ApplyDiscount with AddTax

 The flow:
 super.put(x-x*0.07)->super.put(x*0.8)->put(x)

Thursday, June 20, 2013

Scala => 1) function literal, 2) implicit conversion, 3) Operator overloading 4) Traits discussed

import java.io.PrintWriter

trait Destination { 
 val str:String
 def sendMess
}
class Towers(f:(String)=>Unit, valToSend:String) extends Destination {  
  val str = valToSend  
   def sendMess {  
  f(str) 
}
}

class Churchill(f:(String)=>Unit, valToSend:String) extends Destination {  
   val str = valToSend  
   def sendMess {  
      f(str)  
 }
}

class MyClass(val str:String)

class Sender{
var dests:List[Destination] = List() 
 def +(x:Destination) = dests::=x 
 def sendMessages = for(dest<-dests) dest.sendMess
}
object MyObj { 

def myTest { 
 import MyConversions._  
  val dest1 = new Towers(printContent, "Hi this is Towers")  
  val dest2 = new Churchill(printContent, "Hi this is Churchill")  
  var send = new Sender  
  send+dest1  
  send+dest2  
  send.sendMessages  
  var send1 = new Sender  
  val dest3 = new Towers(printContent, new MyClass("This is implicit Towers message 1"))
  val dest4 = new Churchill(printContent, new MyClass("This is implicit Churchill message 1"))    send1+dest3  
   send1+dest4  
   send1.sendMessages  
} 
def printContent(x:String):Unit= {  
     val pw = new PrintWriter("testObserver.txt", "UTF-8")
     pw.write(x) pw.flush 
   }
}
object MyConversions { 
   implicit def conToStr(x:MyClass):String = x.str
}
MyObj.myTest


EXPLANATION:
The example I tried to show here is the Observer core design pattern. 

Observer Pattern: In this pattern there will be a single source point but multiple destination points. Each destination point is unaware of one another. Therefore they are decoupled and hence addition of new destinations can be done and therefore it enhances maintainability without much rework.
Examples of Observer patterns are
1) Cellphone tower: As soon as the tower bandwidth is up, it notifies all the cellphones within its range.
2) Logs in a web application: We can have our log statement go multiple locations (Application logs, System logs etc..)


The trait Destination encompasses the abstract fields/methods that the concrete destinations implement.
In this example the destinations are 1) Towers & 2) Churchill. These were the two apartments in Minneapolis where I stayed. Each of these classes has a sendMess method which does some work.
There are two parameters of this method. One is a function literal and the other is a String. 

A function literal is closely similar to anonymous classes in java.
Java Anonymous Class: When we do not want to have a sub-class , when we want to have a inline class implementation , when we want to reduce verbosity by writing smaller classes in separate files,  then we can use an anonymous class.

Eg: Consider an Interface Product which has a single method getName() which returns a String. Consider a client which is trying to use a concrete implementation of Product but does not like it having sub-classed for the above mentioned reasons.
public interface Product{
      public String getName();
}
// Method which needs a product type parameter.
public void myMethod(Product prod){
     
}

//Client code which calls the myMethod method using Anonymous Functions
myMethod(new Product{
      @Override // Just to make sure we are overriding the method indeed.
      public String getName() {
              return "Anonymous Product" ;
     }
});

// Client code which calls the myMethod method normally (without anonymous classes)
public class ProductImpl extends Product {
      @Override
       public String getName() {
                return "Anonymous Product";
       }
}
Product prod = new ProductImpl();
myMethod(prod);

The above code increases verbosity and since the class implementation has just one small method , it need not be sub-classed.

Function Literal: On the other hand, a function literal in Scala need not have an enclosing wrapper (unlike anonymous classes in java ) to be able to survive. It is of the form func:(T,M)=>K. Here the function literal's reference is 'func' which takes parameters T & M as input and returns a value of type K. Here the part towards the right side of "=>" is its body and the part towards the left of "=>" are its input parameters. The function's parameters and the body have to be supplied by the client calling the function literal. The function literal can be easily passed as parameters to other functions, as return values, as variable types and even as Type Parameters for eg: MyClass[f:(Int,Float):Boolean] is a class with a Type parameter f:(Int,Float):Boolean. Though we have type parameters in java of the form MyClass<T> , T cannot be a function literal as the Java compiler does not allow that.
As part of Java 8 , there is a Project called Project Lambda which is contemplating on bringing function literals into java and the syntax proposed is something like this.... f:(int, float) <- Boolean.

Coming back to our code...

In our example the function literal f:(String)=>Unit takes a String as an input and returns nothing (Unit). Unit represent the return type of a function which returns nothing. This function literal in our code holds the implementation of the sendMess method of the classes implementing the Destination trait. The implementation of the function literal is supplied by the method printContent. This method will give the concrete implementation of what has to be done when the sendMess method is called from each of the concrete implementations of the Destination trait.
The function literal just creates a PrintWriter from the java.io package and writes the string to the writer.

def printContent(x:String):Unit= {
val pw = new PrintWriter("testObserver.txt", "UTF-8")
pw.write(x)
pw.flush
}
}
By sending the implementation (as a function literal) we are removing the duplication of code within each of the classes which extends Destination. Therefore even if another 10 new destination implementations come, the code need not change and can be easily integrated with the existing code.

Implicit conversions: The variables dest3 and dest4 should ideally parameters 1) F:(String)=>Unit and 2) String. But, these variables take 1) F:(String)=>Unit and 2) MyClass. Therefore the compiler should throw an error. But it does not happen. This is where the implicit conversions come into picture.
The moment when the compiler realizes that something wrong w..r.t the parameters has happened or it sees any compilation errors, it does not give it up and throw an error but checks if there have been any implicit definitions have been made by the user or in the Predef object.

Here in our case when the compiler comes across the code..

val dest3 = new Towers(printContent, new MyClass("This is implicit Towers message 1")).

The part in red is read as a compilation error, then the compiler checks if the user has imported any implicit conversions.
The implicit conversion definition is defined in the MyConversions object with the name conToStr . The name does not matter here but the keyword implicit matters everything. This keyword tells the compiler that there is a conversion that the user is providing for an object of type MyClass. This conversion definition converts this MyClass into a String. This conversion makes the compiler successful in parsing the variable declaration dest3 and dest4. The implicit definition conToStr is defined as follows..


object MyConversions {
implicit def conToStr(x:MyClass):String = x.str
}

The definition just takes an object of MyClass and returns a String and remember to import this object for the conversion to apply.
import MyConversions._

Operator Overloading: The operator + is overloaded by the users own implementation. This is similar to adding an object to another.i.e. obj1.add(obj2) is simplified to obj1+obj2. This code adds the obj2 to obj1. The custom definition of the operator "+" is give just the same way any other function is defined.
for e.g: def methodName(Parameters) = def +(x:Destination): Unit
Here the method name is replaced with "+" and it can be used as a+b instead of a .+(b) which is the underhood implemtation which Scala provides.


CODE FLOW EXPLANATION:
1) A trait (named Destination) which abstracts the functions a destination provides is created. Provide concrete implementations to this trait with Names Churchill and Towers. These are the places where the message has to be delivered.
2) The concrete classes implement the sendMess method by calling the function literal f.
3) Create a Sender class which collects all the destinations which the user wants to send his message to. This class defines a "+" method which adds new destinations supplied by the client calling it. The method sendMessages iterates through the Destinations and calls the sendMess method on each of the destination.
4) Create MyConversions object and import it as import MyConversions._
5) The client code MyObj has a method myTest which instantiates the Churchill and Towers classes, adds them to the Sender object and calls the sendMessages on the Sender object. This will eventually call the function literal f:(String)=>Unit on each of the concrete Destination classes and the message will be delivered by writing the message to a file with the name testObserver.txt.

Advantages:
1) Enhance re usability through traits.
2) Function literals enhance robustness and make code less verbose.
3) Implicit conversions make integration with legacy java libraries easy.
4) Operator overloading makes using operators with their own syntax but custom defined implementation.









Friday, June 14, 2013

Simple custom Stack[Type] implementation in Scala.

trait MyStack[T] {
 def push(x:T):MyStack[T]
 def pop:MyStack[T]
 def isEmpty:Boolean
 def top:T
 def length:Int
}

object MyStack {

 def apply[M](xs:M*):MyStack[M] = new MyStackImpl[M](xs.toList)

 class MyStackImpl[M](var xs:List[M]) extends MyStack[M] {

  def push(pu:M):MyStackImpl[M] = new MyStackImpl[M](pu::xs)

  def pop:MyStackImpl[M] = new MyStackImpl(xs.tail)

  def top:M = xs.head

  def isEmpty:Boolean = if(xs.length==0) true else false
                def head:M = xs.head

  def length:Int = xs.length
 }
}

def myTest {
val stack1 = MyStack(1,2,3,4,5) // This is similar to Mystack apply (1,2,3,4,5)
println(stack1.top) // Prints the top most element of the stack
val stack2 = stack1.push(5)
println(stack2.top) // After 5 is added to the top, this statement prints 5 as the top element
println(stack2.pop.top) // This statement pops the top element (5) and prints the top element (1)
println(stack2.pop.pop.pop.pop.pop.pop.length) // This will print 0 since all the elements of                          the stack are popped.
}
myTest


//OUTPUT
1
5
1
0


Explanation:
1) Create a Typed trait (MyStack[T])which can be used to create Stacks that can accommodate any types.
2) The companion object is MyStack (with the same name as that of the trait). This becomes a  companion object of all the concrete classes which are mixed with the trait MyStack. This will ensure the object has access to the private members (if any, in this case we don't have private members) of the concrete classes.
3) The apply method of the object has to be implemented. The input parameter is a sequence of
elements of type M. The sequence will be converted into a List (using seq.toList method) for processing.
The apply method allows us to use the class as a Factory. for eg: MyStack(1,2,3,4,5) which has the implementation MyStack.apply(1,2,3,4,5) under the hood.
4) This class MyStackImpl  is the concrete implementation of the custom Stack trait. The constructor is a list
which was created using the Sequence element from the input parameter of the apply method present
in this object.
5) The push method of the MyStackImpl object adds an element of type M at the top of the stack and returns a new MyStackImpl object with the newly added element at the top
6) The pop method of the MyStackImpl  removes(pops) the top most element from the stack and returns another MyStackImpl object without the popped element.
7) The top method returns the top most element from the stack.
8) The isEmpty methods returns true if there are no elements in the stack and false when at least one element is present.
9) This length function returns the length of the stack.




Create your own 'typed' Map implementation in Scala


trait MyMap[K,V] {
 def get(x:K):V
 def +(x:K, y:V):MyMap[K,V]
 def isEmpty:Boolean
 def keys:List[K]
 def values:List[V]
}
object MyMap{
 def apply[K,V](x:Tuple2[K,V]*):MyMap[K,V] = new MyMapImpl[K,V](x.toList)
 class MyMapImpl[T,M](var myMap:List[Tuple2[T,M]]) extends MyMap[T,M] {
  def get(x:T):M = {
   for((key,value)<-myMap) {
    if(key==x)
    return value
   }
   throw new Exception("Value not found!!!")
  }
  def +(x:T,y:M):MyMapImpl[T,M] = {
   for((key,value)<-myMap){
    if(key==x)
    throw new Exception("Key value already exists")
   }
   myMap::=Tuple2(x,y)
   new MyMapImpl[T,M](myMap)
  }
  def isEmpty:Boolean = if (myMap.length==0) true else false
  def keys:List[T] = for((key,value)<-myMap) yield {key}
  def values:List[M] = for((key,value)<-myMap) yield {value}
 }
}
def myTest {
 val myMap1 = MyMap((1,"Sandeep"),(2,"Praveen"),(3,"Vikas"),(4,"Suresh"))
 println(myMap1.get(3)) // Getting the value with key 3
 println(myMap1.keys) // Displaying a list of all keys
 println(myMap1.values) // Displaying a list of all values

 val myMap2 = myMap1+(5,"Pranay") // Add a key value pair to our custom Map(MyMap)

 println(myMap2.keys) // Displaying the list of keys of our map including '5' added in previous line
 println(myMap2.values)
}
myTest



//Output
Vikas
List(1, 2, 3, 4)
List(Sandeep, Praveen, Vikas, Suresh)
true
List(5, 1, 2, 3, 4)
List(Pranay, Sandeep, Praveen, Vikas, Suresh)


Explanation:

We have a predefined Map definition under the scala.predef object. Using similar methods present in the Map object we can come up with our own definition of the Map.

The underlying data structure we will be using is a List, therefore the results will all be in the form of lists.

1) Define a trait with a preferred name (say MyMap) and define all the abstract methods which your custom map should expose. Make sure your map is typed so that we can create a map of any object type say MyMap[Any,Any].
2) Define an object (with the same name MyMap), this object is the companion object of all the concrete classes which mixes in with the trait MyMap.
3) Override the apply function in the object . The input parameters of the apply function will be a sequence of Tuple2 's. x:Tuple2[K,V]* indicates patterns of the form (a,b),(c,d)(e,f) (where every alphabet represents any object in scala, Any)
4) Write a class inside the MyMap object which will be a concrete implementation of the trait MyMap. Lets say the concrete class is MyMapImpl which takes arguments of type List[Tuple2[K,V]] which means it a list of Tuple2's of type[K,V] which are key & value pairs. We should make sure we represent the variable of this type is a var and not a val.
5) Implement the methods in the MapTrait. One of the method here needs special attention. The "+" operator is now an overloaded method which is used to add a mapping entry into the custom map. This operator takes the Tuple as an input and return an object of the for MyMapImpl which also includes the newly added mapping.

6) Once the map is ready, write a test method (say myTest) and test the functionality of the methods you just implemented.