Chapter 18: traits and delegation, built

Chapter 18 builds a trait, View, with no state of its own - and two classes that take it on two different ways: Home implements it, Framed delegates to a field it holds. The IDE's own tools make the structure - the trait's shell, each method's whole signature, the trait adoption itself, the field, the constructors' shells - so what is left to type is only the logic no tool can write. To play it in the IDE: Help > Lessons > Writing code…, or ek9 -ide -lesson book-traits.

The EK9 IDE as the lesson opens

What you'll build

The lesson builds this program from a file holding only its module line - the finished file, as the book prints it.

#!ek9
defines module book.delegate

  defines trait
    View
      title() as pure
        <- rtn as String?
      body() as pure
        <- rtn as String?

  defines class
    Home with trait of View
      override title() as pure
        <- rtn as String: "Home"
      override body() as pure
        <- rtn as String: "Welcome"
      default operator ?

    Framed with trait of View by inner
      inner as View?

      default private Framed() as pure

      Framed() as pure
        -> viewToWrap as View
        require viewToWrap?
        inner :=? viewToWrap

      override body() as pure
        <- rtn as String: `*** ${inner.body()} ***`

      default operator ?

  defines program
    Show()
      stdout <- Stdout()
      page <- Framed(Home())
      stdout.println(`title: ${page.title()}`)
      stdout.println(`body:  ${page.body()}`)

Step 1: A trait

Choose Code > New Construct > Trait…

The EK9 IDE once this is done: Choose Code > New Construct > Trait…

View is the trait's name. Type View and choose Preview.

The EK9 IDE just before choosing Preview: View is the trait's name

The preview shows View about to be written - a shell, nothing inside it yet. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows View about to be written - a shell, nothing inside it yet

Right-click the View line and choose Add Method…

The EK9 IDE once this is done: Right-click the View line and choose Add Method…

title is the name, String the return type, pure ticked - the dialog has no way to leave a method bodyless without also writing 'abstract', which View does not use (no body is already enough, as the chapter explains), so a plain stub is made here and typed next. Fill them in and choose Apply.

The EK9 IDE just before choosing Apply: title is the name, String the return type, pure ticked - the dialog has no way to leave a method bodyless without also writing 'abstract', which View does not use (no body is already enough, as the chapter explains), so a plain stub is made here and typed next

Select the stub, from <- rtn as String: String() down to its //AI line, and type <- rtn as String? - no value: that is what makes title abstract. Saves when typed.

The EK9 IDE once this is done: Select the stub, from <- rtn as String

Right-click the View line and choose Add Method…

The EK9 IDE once this is done: Right-click the View line and choose Add Method…

body, the same way - String, pure. Fill them in and choose Apply.

The EK9 IDE just before choosing Apply: body, the same way - String, pure

Select the stub, from <- rtn as String: String() down to its //AI line, and type <- rtn as String? again. Saves when typed.

The EK9 IDE once this is done: Select the stub, from <- rtn as String

Step 2: Home — implementing it

Choose Code > New Construct > Class…

The EK9 IDE once this is done: Choose Code > New Construct > Class…

Home is the class's name. Type Home and choose Preview.

The EK9 IDE just before choosing Preview: Home is the class's name

The preview shows Home about to be written. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows Home about to be written

Right-click the Home line and choose Adopt Trait…

The EK9 IDE once this is done: Right-click the Home line and choose Adopt Trait…

View is the trait - Implement method stubs is already chosen, the other way being to delegate to a field (Framed's way, next). Type View and choose OK.

The EK9 IDE just before choosing OK: View is the trait - Implement method stubs is already chosen, the other way being to delegate to a field (Framed's way, next)

Adopt Trait changes the open file in the editor, not yet saved. Choose File > Save.

The EK9 IDE once this is done: Adopt Trait changes the open file in the editor, not yet saved

Select title's stub, from <- rtn as String: String() down to its //AI line, and type <- rtn as String: "Home" - title's whole answer. Saves when typed.

The EK9 IDE once this is done: Select title's stub, from <- rtn as String

Select body's stub the same way, and type <- rtn as String: "Welcome". Saves when typed.

The EK9 IDE once this is done: Select body's stub the same way, and type <- rtn as String

Right-click the Home line, choose Add Operator ▸ ? (isSet) ▸ default - Home has no field of its own to make this one for it.

The EK9 IDE once this is done: Right-click the Home line, choose Add Operator ▸ ? (isSet) ▸ default - Home has no field of its own to make this one for it

Step 3: Framed — the field and delegation

Choose Code > New Construct > Class…

The EK9 IDE once this is done: Choose Code > New Construct > Class…

Framed is the class's name. Type Framed and choose Preview.

The EK9 IDE just before choosing Preview: Framed is the class's name

The preview shows Framed about to be written. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows Framed about to be written

Right-click the Framed line, choose Add Constructor ▸ Private No-Arg (default, pure) - blocking plain construction, added FIRST, while Framed has no field yet.

The EK9 IDE once this is done: Right-click the Framed line, choose Add Constructor ▸ Private No-Arg (default, pure) - blocking plain construction, added FIRST, while Framed has no field yet

Right-click the Framed line and choose Add Field…

The EK9 IDE once this is done: Right-click the Framed line and choose Add Field…

Type inner as View? - a field whose type is the trait itself, left unset until a view is given to wrap. The Take it in a new constructor box ticks itself for a field with no value like this one - that constructor is what the no-arg one just added was for. Choose OK.

The EK9 IDE just before choosing OK: Type inner as View? - a field whose type is the trait itself, left unset until a view is given to wrap

The preview shows inner added, a new pure constructor over it straight after the one already there, and default operator ? too, as Framed now carries a field. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows inner added, a new pure constructor over it straight after the one already there, and default operator ? too, as Framed now carries a field

Right-click the Framed line and choose Adopt Trait…

The EK9 IDE once this is done: Right-click the Framed line and choose Adopt Trait…

View is the trait again - this time choose Delegate to field, and pick inner: Framed already holds one. Choose OK.

The EK9 IDE just before choosing OK: View is the trait again - this time choose Delegate to field, and pick inner

Adopt Trait changes the open file in the editor, not yet saved. Choose File > Save.

The EK9 IDE once this is done: Adopt Trait changes the open file in the editor, not yet saved

Step 4: Framed — construction and body

Select the stub, from Framed() as pure down to this.inner :=? inner, and type Framed's real one: a renamed parameter, viewToWrap, a require guard, and the assignment it guards. Saves when typed.

The EK9 IDE once this is done: Select the stub, from Framed() as pure down to this.inner :=? inner, and type Framed's real one

Right-click the Framed line and choose Add Method…

The EK9 IDE once this is done: Right-click the Framed line and choose Add Method…

body, String, pure, and override ticked - View already declares it. Fill them in and choose Apply.

The EK9 IDE just before choosing Apply: body, String, pure, and override ticked - View already declares it

Select the stub, from <- rtn as String: String() down to its //AI line, and type the wrapping: <- rtn as String: *** ${inner.body()} *** - title is left alone, forwarded by 'by inner'. Saves when typed.

The EK9 IDE once this is done: Select the stub, from <- rtn as String

Step 5: The program

Choose Code > New Construct > Program…

The EK9 IDE once this is done: Choose Code > New Construct > Program…

Show is the program's name - no application to bind, no parameters, nothing injected. Type Show and choose Preview.

The EK9 IDE just before choosing Preview: Show is the program's name - no application to bind, no parameters, nothing injected

The preview shows Show about to be written. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows Show about to be written

Select the stub, from ready <- true down to its //AI line, and type Show's body: a Stdout, a Framed wrapping a Home, and a line each for its title and body. Saves when typed.

The EK9 IDE once this is done: Select the stub, from ready <- true down to its //AI line, and type Show's body

Step 6: Run it

Choose Run > Run Program…

The EK9 IDE once this is done: Choose Run > Run Program…

The project's only program is already chosen. Choose Run.

title: Home, body: *** Welcome *** - title passes straight through the delegation, body is wrapped. Choose Close.

The EK9 IDE just before choosing Close: title

The result

Delegate.ek9 now reads exactly as chapter 18 of Getting Started with EK9 shows it: a trait the tools built, Home implementing it and Framed delegating to a field it holds, logic you typed. The same tools - ek9_new_construct, ek9_add_method, ek9_adopt_trait, ek9_add_field, ek9_add_constructor, ek9_add_operator, ek9_add_program - are what an AI assistant calls to do the same thing.

The EK9 IDE when the lesson has finished

Where this lesson comes from