#1821 what's the equivalent of extension method in fantom

taojoe Thu 8 Mar 2012

hi all,

I've read some topic here, it seems that fantom is not support this feature. is that right?

could brian explain this more?

Joe

SlimerDude Thu 8 Mar 2012

Are you refering to this topic? : What do you think about Xtend?

dobesv Thu 8 Mar 2012

Well that thread doesn't talk much about extension methods, just whether or not to re-use Java libraries or build a new ecosystem.

I think extension methods are cool, I really liked them in Objective-C where they were quite well used.

I guess the biggest challenge with extension methods is deciding when they are "in scope". In Objective-C you're adding methods globally to the classes and this seems to be OK but you also explicitly specify all the source files you compile in your project so that's how you decide which extensions apply and which don't.

I think maybe in Fantom you could have a new kind of using statement like:

extend sys::Str with myext::StrPlusPlus

Where StrPlusPlus would be a trait whose abstract methods line up with concrete methods of Str. Then within that module you can use methods defined in StrPlusPlus on anything declared as a string. Much like C# the binding would have to be static, it's not a way to really add methods to the core classes.

Adding methods to the core classes has a greater risk of collisions. I suppose all the extensions defined in your pod or the pods you depend on would be added as actual methods to the target classes.

brian Thu 8 Mar 2012

I've read some topic here, it seems that fantom is not support this feature. is that right?

Fantom doesn't have currently extension methods in the C# sense. We have talked about adding them similar to C# design.

Although remember the Fantom community isn't Oracle or Microsoft either :-) If there is a generally useful method you'd like to see added to a built-in type, just post your ideas or a patch and we see if it makes sense to add it.

taojoe Fri 9 Mar 2012

great to know that. I'm new to Fantom, and I like it, it makes programming clear. Once I have good ideas, I'll post here. patching is far away for me.

SlimerDude Tue 17 Jun 2014

I know that Axon (Sky Foundry script) does some neat stuff with static methods to allow a form of extension; I'm not sure if the Fantom compiler could do the same...?

Anyway, I thought of a half-way house. It may not be my best idea (!) but it goes like this...

On maps and lists we have a useful map() method for converting multiple object instances:

values    := [1, 2, 3]
mongoDocs := values.map { convertToMongo(it) }

So how about adding a generic map() method to Obj. It would then be used like this:

value     := 2
mongoDoc  := value.map  { convertToMongo(it) }

Though it would have to return Obj for now until generic are sorted - but type inference helps out there. The method on Obj would essentially just be:

Obj? map(|Obj item->Obj?| c) { 
  return c.call(this) 
}

That would then allow you to chain methods together in a more consistent manner - and you're not limited to just static methods either.

SlimerDude Wed 18 Jun 2014

I was just thinking that you could go some way to accomplishing extension methods with dynamic calls.

The following example has a Registry that stores the extension functions, keyed on type and method name. Any object wishing to make use of it simply overrides their trap() method:

using afConcurrent

class Example {
    Void main() {        
        // add your extension methods
        XtendRegistry.add(MyObj#, "toInt") |MyObj obj->Int| { obj.int }
        XtendRegistry.add(MyObj#, "add"  ) |MyObj obj, Int a->MyObj| { 
            obj.int += a
            return obj 
        }
        
        // try them out!
        MyObj()->toInt
        MyObj()->add(3)->toStr
    }
}

class MyObj {
    Int int
    
    // refer all dynamic calls the the XtendRegistry
    override Obj? trap(Str name, Obj?[]? args := null) {
        XtendRegistry.xtend(this, name, args) ?: super.trap(name, args)
    }
}

const class XtendRegistry {
    private static const AtomicMap registry := AtomicMap()
    
    static Obj? xtend(Obj xtends, Str name, Obj?[]? args) {
        key := "${xtends.typeof.qname}->${name}"
        if (!registry.containsKey(key))
            return null
        func := (Func) registry[key]
        return func.callList([xtends, args ?: Obj#.emptyList].flatten)
    }
    
    static Void add(Type xtendType, Str name, Func func) {
        key := "${xtendType.qname}->${name}"
        registry[key] = func
    }
}

You could take it further by using an @Xtend facet or similar to mark extension methods, and the Registry could scan for them.

But I feel it's all pretty academic because it's only useful for your own objects. You still can't extend Ints, Strs or Maps, etc...

Maybe Obj.trap() could defer to some sort of global system immutable func / listener that you could overwrite or contribute to? But I feel like I'm just rambling now...

brian Wed 18 Jun 2014

I personally don't really mind static utility methods, but sometimes that does get a little noisy.

I like the trap idea and that is what I was thinking myself (but you are right that only works for your objects)

elyashiv Thu 24 Sep 2015

A little late, but a few thought of my own -

  1. Extension methods like in c# have a lot of power, but they have one big problem - your code is harder to read, as you start using more and more functions witch are not declared in a specific place. I mean, if you well look at the following code: L := [1,2,3]; L.Select { it.isOdd } where well you look for documentation and code for the Select function?
  2. For the same reason the code is not as reusable - if I want to copy a chunk of code someone used, I need to find the extension function, though I don't know where he defined it.
  3. Is it that bad to write ListTools.Select(L) { it.isOdd }? same functionality, just now I overcome the problem I mentioned above - the function Select is part of ListTools. Did it hurt?

SlimerDude Thu 24 Sep 2015

Hi elyashiv,

I totally agree - extension methods make the code harder to understand as you're essentially mixing up 2 separate APIs. And unless you follow the using statements, you're not going to know where the methods came either.

If Fantom did offer extension methods, I'm not convinced I would use them. At least, not extensively.

But, I am not everyone, and others would argue that extension methods make their code neater, moar succinct, and easier to read. True, it could be abused - but the same could be said for any feature.

I just think it'd be nice to give people the option.

I quite like the trap() idea (or something similar) - if only because the syntax looks different and clues in the reader that it's not a normal method call.

elyashiv Fri 25 Sep 2015

The trap() idea is a nice piece of code, and one pro of it is that it doesn't allow to extend any class, which can hold back the mess extension methods could create.

A few downs to this approach are the compile time checks (which you can't get unless you change the build class, and the run time consumption - you add some more code to each function call in that class.

Login or Signup to reply.