Saturday, October 24, 2009

Clone objects with arguments override in Scala 2.8: best practices ?

I'm actively looking for best practices and patterns about how to create clone objects in Scala, with the possibility to change some values of the cloned object along the way.

I have the usual concern of the Java world on that topic: I would like to avoid Java "clone", I want to be able to use the pattern in a class hierarchy, I want to have the minimum amount of code (or at least, the simplest possible) to write in domain class to support my cloning API.
And as I'm in Scala, I want to deal with both vars and vals, if possible in the same consistent way.

Actually, I want to provide to my libraries consumer an effective way to do things like that (where *Child* extends *Parent* class):



    val p = new Parent("some value")
    // simple cloning
    val p2 = p.copy
    //clone, and change a val and a var along the way
    val p3 = p.copy(p_val1 = "new value for val", p_var1 = "new value for var")

    //and same things apply for children of Parent:
    val c = new Child("some parent value", "some child value")
    //override child val/var and/or parent val/var in the same fashion
    val c1 = c.copy(p_val1 = "new value for val defined in Parent class",
                    c_val1 = "new value for val defined in Child class",
                    p_var1 = "new value for var defined in Parent class",
                    c_var1 = "new value for var defined in Child class" )


As Scala 2.8 is advertised with "default and named arguments", such things should possible (well, hopefully easy too ;). I came with this solution:



class A(val val_a:String)  {
    var var_a = ""
}

object A {
    def merge[T](t:T)
    (
        var_a : String
    ) : T = {
        t match {
            case x:A => x.var_a = var_a
            case _ => error("")
        }
        t
    }
    
    def copy(source:A)
    (
        val_a : String = source.val_a, 
        var_a : String = source.var_a
    ) : A = merge(new A(val_a))(var_a)
}

class B(override val val_a:String, val val_b : Int) extends A(val_a) {
    var var_b = 0
}

object B {
    def merge[T](t:T)
    (
        var_a : String,
        var_b : Int
    ) : T = {
        A.merge(t)(var_a) match {
            case x:B => x.var_b = var_b
            case _ => error("")
        }
        t
    }
    
    def copy(source:B)
    (
        val_a : String = source.val_a,
        var_a : String = source.var_a,
        val_b : Int = source.val_b,
        var_b : Int = source.var_b
    ) : B = merge(new B(val_a,val_b))(var_a,var_b)
}


class C(override val val_a : String) extends A(val_a)

// ***************** 
// Example of use
// ***************** 

object TestCloning {
    
    def main(args:Array[String]) {
        val a1 = new A("la_init")
        a1.var_a = "ra_init"
            
        val b1 = new B("lb_init",1)
        b1.var_a = "rb_init"
        b1.var_b = 10

        //example with A

        val a2 = A.copy(a1)()
        assert(!(a1 eq a2))
        assert(a2.var_a == a1.var_a)
        assert(a2.val_a == a1.val_a)
        
        val a3 = A.copy(a1)(val_a = "la_mod")
        assert(a3.var_a == a1.var_a)
        assert(a3.val_a == "la_mod")
        
        val a4 = A.copy(a1)(val_a = "la_mod", var_a = "ra_mod")
        assert(a4.val_a == "la_mod")
        assert(a4.var_a == "ra_mod")

        val a5 = A.copy(new C("foo"))()
        assert(a5.var_a == "")
        assert(a5.val_a == "foo")

        val a6 = A.copy(b1)()
        assert(a6.val_a == "lb_init")
        assert(a6.var_a == "rb_init")
        
        //with B
            
        val b2 = B.copy(b1)()
        assert(b2.val_a == "lb_init")
        assert(b2.var_a == "rb_init")
        assert(b2.val_b == 1)
        assert(b2.var_b == 10)
            
        val b3 = B.copy(b1)(var_b = 5, val_a = "lb_mod")
        assert(b3.val_a == "lb_mod")
        assert(b3.var_a == "rb_init")
        assert(b3.val_b == 1)
        assert(b3.var_b == 5)
            
    }

That solution seems to work, and allows to do the kind of use case I wanted to build.
But it seems not satisfactory at all for (at least) these reasons:
1/ - the trailing "()" in real clone without override is mandatory and doesn't look right;
2/ - it seems to be a lot of tedious code to write in each class, and so seems to be really error prone;
3/ - I'm not at ease with the merge method, but I can't say why exactly;
4/ - I don't like to much the factory approach.

1/ could be easily addressed if in place of only a copy(source:X)(.....) we define a couple of method :
    def copyWith(source:A)( ... ) : A = merge(...)(...)
    def copy(source:A) = copyWith(source)()

3/ and 4/ are just feelings, and so could be shut up for now.

But 2/ is a real concern, and I would like to find a way to externalize more code in traits, so that domain classes have only simple, short code to write to provide a clone API.
 
So Scalazy, what are your best practices about the cloning topic in Scala ?

Monday, October 12, 2009

ActiveLDAP in Scala : LDAP on the JVM, in a shell !

Update: typos only

Until a really recent time, LDAP in the JVM, trougth JNDI API, was a nightmare of usability. Just connecting to an LDAP directory with a simple login/pass was worth a dozen lines of really unatural code (and I'm almost not exagering)

Hopefully, the situation is evolving, and there is several projects willing to provide a better LDAP SDK on the JVM.

Among them, there is UnboundId's one. I started to play with it, and it's quite delighting to be able to use a good API to do your work !

So it gives me an idea : how this SDK could be pimped thanks to Scala to be used in command lines, in a ruby ActiveLDAP fashion ?

And things are coming along really well. It's just a start, but this is an example of a Scala REPL session with my version of Scala ActiveLDAP :


scala> import test.activeldap._
import test.activeldap._

scala> import LdapFilter._
import LdapFilter._

scala> val p = new SimpleAuthLCP(baseDn = "dc=example,dc=org",authDn = 
"cn=admin", authPw = "secret pass")
p: test.activeldap.SimpleAuthLCP = [cn=admin@localhost:389 (base: 
dc=example,dc=org) by password authentication]

scala> val users = new MetaActiveEntry(prefix = "ou=people",  
rdn = "uid" , classes = Set("top","person","organizationalPerson","inetOrgPerson"), 
provider = p )
users: test.activeldap.MetaActiveEntry = test.activeldap.MetaActiveEntry@c85a33

scala> val user = users.find()(0)
user: test.activeldap.ActiveEntry = uid=42,ou=people,dc=example,dc=org

scala> user.details
res28: java.lang.String = Entry(dn='uid=42,ou=people,dc=example,dc=org', 
attributes={Attribute(name=objectClass, values={'inetOrgPerson', 
'organizationalPerson', 'person', 'top'}), 
Attribute(name=sn, values={'Bar'}), Attribute(name=cn, values={'Foo'}), 
Attribute(name=mail, values={'foo@bar.com'}), Attribute(name=uid, values={'42'})})

scala> user("uid") = "43"

scala> user.save

scala> users.find()
res31: Seq[test.activeldap.ActiveEntry] = ArrayBuffer(uid=42,ou=people,dc=example,dc=org,
uid=43,ou=people,dc=example,dc=org)

scala> user("mail") = Seq("foo@bar.com","foo_bar@bar.com")

scala> user.save

scala> users.find(EQ("uid","43"))(0).details
res34: java.lang.String = Entry(dn='uid=43,ou=people,dc=example,dc=org', 
attributes={Attribute(name=objectClass, values={'inetOrgPerson', 
'organizationalPerson', 'person', 'top'}), Attribute(name=sn, values={'Bar'}),
Attribute(name=cn, values={'Foo'}), Attribute(name=mail, values={'foo@bar.com', 
'foo_bar@bar.com'}), Attribute(name=uid, values={'43'})})

scala> user.delete

scala> users.find(EQ("uid","43"))
res36: Seq[test.activeldap.ActiveEntry] = ArrayBuffer()


scala> 

There is a really nice feature brought by Scala 2.8 for this kind of API: named and default parameters. I  used it in the previous sessions to define the connection: host and port were not provided (default to localhost:389),  but you can specify them if you need :

 scala> val p = new SimpleAuthLCP(
        | authDn = "cn=manager",
        | authPw = "secret password",
        | host = "an.other.host.com",
        | port = "1389",
        | baseDn = "dc=company,dc=com"
        | )

It's just a begining, and there is a lot of other nice features from Ruby ActiveLDAP that could be added...

Thursday, October 1, 2009

Alternative languages for the JVM @ OpenWorldForum Paris

Today, a was kindly invited by Alexis Moussine - Pouchkine to be the Scala advocate in a roundtable about alternative languages on the JVM, during a session about the Futur of Java in Open World Forum meeting in Paris.

The sessions was brief (3 parts of about 30 minutes), and in my feeling a bit outside the main topic of OpenWorldForum which was more about open source at a strategic an politic level, but (surprisingly for me) our room was quite crowd, with interested people.

Alexis gave the first presentation about the state of Java and OpenJDK, and final presentation,  about forthcoming JavaEE 6. As always, I really liked to hear and see Alexis make the show, his presentations were really good, and he defenitly deserve his "JavaEE and Glashfish Evangelist" title.

The roundtable begun with a presentation from Stéphane Fermigier of the way accomplished by Java and the JVM as a platform for other language since it's first release back in 1996.
Afterwards, Guillaume Laforge (of course for Groovy) and I (for Scala) talked about our prefered language, the "welcomeness" of the JVM plateform, the always funny debate about dynamically and statically typed languages, and the fact that we seem to be going to a world of "polyglotism", where multiple languages would cooperate on top of a highly industrialized, robust and efficient VM, and be selected for their adequacy to the task to accomplish - all that things mixed up with attendees questions. 

And then, even if we went past the given 30 minutes (well, actually, even went past the 40 minutes...) it was already time to stop.

It was a really pleasant meeting, and I'm really happy to see that Groovy is now a first class citizen in the Java world, that Scala is beyond the status of new intriguing thing and becomes to be evaluated in different places, and that we can say "functional programming" elsewhere than in an University or some strange startup without being looked as a dangerous, non business compliant hacker.

  © Blogger template 'Minimalist G' by Ourblogtemplates.com 2008

Back to TOP