code-cookbook icon indicating copy to clipboard operation
code-cookbook copied to clipboard

Array access word list

Open hamaluik opened this issue 8 years ago • 3 comments

Array access of words in a sentence

Abstract types are very useful for layering strongly-typed functionality over top of more basic classes. They are also the only place where operator overloading (including array access). In this relatively trivial example, we can make use of the @:arrayAccess metadata to allow us to easily access individual words in a sentence (where word boundaries are defined as spaces).

First off, we can describe an abstract over a string, which will define a new type that is 100% a string:

abstract WordList(String) from String to String {
  public function new(s:String)
    this = s;
}

This is a bit useless on it's own however, so let's add the @:arrayAccess components we're actually after:

abstract WordList(String) from String to String {
  public function new(s:String)
    this = s;

  @:arrayAccess
  public inline function getWord(index:Int) {
    return this.split(' ')[index];
  }

  @:arrayAccess
  public inline function setWord(index:Int, word:String) {
    var words:Array<String> = this.split(' ');
    words[index] = word;
    return this = words.join(' ');
  }
}

These two functions (getWord and setWord) are simple inline functions for extracting or replacing a word in a sentence, and could be called as:

var thirdWord = sentence.getWord(2);
sentence.setWord(1, "moderately-paced");

However, this is a bit verbose. The @:arrayAccess metadata allows us to instead call the functions as:

var thirdWord = sentence[2];
sentence[1] = "moderately-paced";

Usage

var sentence:WordList = "The quick brown fox jumped over the lazy dog's back.";

Sys.println('In the sentence:');
Sys.println(' > ' + sentence);
Sys.println('');

Sys.println('What colour is the fox?');
Sys.println(' > ' + sentence[2]);
Sys.println('');

Sys.println("Let's slow him down...");
sentence[1] = "moderately-paced";
Sys.println(' > ' + sentence);

This will print:

In the sentence:
 > The quick brown fox jumped over the lazy dog's back.

What colour is the fox?
 > brown

Let's slow him down...
 > The moderately-paced brown fox jumped over the lazy dog's back.

More on this topic:

Author: Kenton Hamaluik

hamaluik avatar Oct 06 '17 22:10 hamaluik

Doing a string split on each array access looks painful... Shouldn't you define that abstract over Array<String> and do the split only in the constructor?

Simn avatar Oct 06 '17 22:10 Simn

That was actually the original code:

abstract WordList(Array<String>) {
    public function new(words:Array<String>)
        this = words;

    @:from
    public inline static function fromString(s:String)
        return new WordList(s.split(' '));

    @:to
    public inline function toString()
        return this.join(' ');

    @:arrayAccess
    inline function getWord(index:Int) {
        return this[index];
    }

    @:arrayAccess
    inline function setWord(index:Int, word:String) {
        return this[index] = word;
    }
}

But I decided to just abstract it over a string to focus on the @:arrayAccess components. As per #49 I was looking for a relatively simple example showing how to use @:arrayAccess rather than how to do this particular operation most efficiently.

That said, I'd be happy to change it back, or add this version into the article (with a short discussion of why this would be done instead of the first way) if you think that would be more beneficial.

hamaluik avatar Oct 06 '17 23:10 hamaluik

Hi, thanks for the contribution. I agree with @Simn, can you change it so it doesn't split on each access? While I get it's not the point of the article, we should strive to have snippets with good performance.

I would suggest to do something like this:

abstract WordList(Array<String>) {
    public function new(word:String, delimiter:String = " ")
        this = word.split(delimiter);

markknol avatar Nov 21 '17 07:11 markknol

Closing due to lack of follow-up.

Simn avatar Jan 23 '24 20:01 Simn