#882 Just for fun: mixin with list-specific methods

ivan Tue 22 Dec 2009

Sometimes I need common functions like each, eachr, reduce etc. in my classes which encapsulate lists. However writing all these methods (which just delegate to encapsulated list) is quite boring.

Then I noticed that it is enough to have only eachWhile implementation to write others (but not in most efficient way), so I wrote a simple mixin:

mixin Iterable
{
  ** Inheritors just need to implement this method
  abstract Obj? eachWhile(|Obj->Obj?| f)

  ** Implementation based on `#eachWhile`
  Void each(|Obj| f) { eachWhile |a| { f(a); return null } }

  ** Implementation based on `#each`
  Obj? reduce(Obj? init, |Obj? r, Obj item->Obj?| f)
  {
    result := init
    each |i| { result = f(result, i) }
    return result
  }

  ** inefficient, override if necessary
  virtual Obj? eachrWhile(|Obj->Obj?| f)
  {
    stack := [,]; each |i| { stack.push(i) } 
    while(!stack.isEmpty) { fs := f(stack.pop); if(fs != null) return fs }
    return null
  }

  Void eachr(|Obj| f) { eachrWhile |a| { f(a); return null } }

  Obj find(|Obj->Bool| f) { eachWhile |i| { f(i) ? i : null } }

  Obj[] findAll(|Obj->Bool| f) { reduce([,]) |Obj[] r, i| { f(i) ? r.add(i) : r } }

  Obj[] map(|Obj->Obj?| f) { reduce([,]) |Obj?[] r, i| { r.add(f(i)) } }
}

Then, just implement eachWhile in my class:

class StrListHolder : Iterable
{
  Str[] list
  new make(Str[] list) { this.list = list }

  override Obj? eachWhile(|Obj->Obj?| f) { list.eachWhile(f) }
}

And the rest is done automatically:

holder := StrListHolder(["one", "two", "three", "four", "five"])

holder.each |Str s| { echo(s) }
holder.eachr |Str s| { echo(s) }

holder.find |Str s->Bool| { s.size == 5 } // "three"
holder.findAll |Str s->Bool| { s.size == 3 } // [one, two]
holder.reduce(StrBuf()) |StrBuf r, Str item -> Obj| { r.add(item) } //concat
holder.map |Str s->Obj| { s.size } //[3, 3, 5, 4, 4]

EDIT: corrected small error in eachrWhile

KevinKelley Tue 22 Dec 2009

Works for any and all too --

Bool any(|Obj, Int->Bool| f)
{ eachWhile |v,i->Obj?| { if (f.call(v,i)) return true; return null } != null }

Bool all(|Obj, Int->Bool| f) { !any |v,i->Bool| { !f.call(v,i) } }

ivan Tue 22 Dec 2009

oh yes, forgot about any and all. My version of any and all looks a bit different:

Bool any(|Obj->Bool| f) { eachWhile { f(it) ? "" : null } != null }
Bool all(|Obj->Bool| f) { eachWhile { f(it) ? null : "" } == null }

KevinKelley Tue 22 Dec 2009

Yeah, those are prettier. :-) This is a pretty handy mixin to have around.

Login or Signup to reply.