Penflip

Penflip - Write better with others

  • Loading...
  • Discover Projects
  • Help
  • Signup
  • Login
  • Welcome back!
    No account? Signup Close
    Ready to write better?
    Have an account? Login Close

Hertz · Programming Language Fantom 1.0

Make Changes
10

13 - Declarative Programming and It Blocks

Chapter 8 described how Fantom uses closures to represent functions that are used as data, for example as parameters to standard library functions.

Closures in Fantom can be written with a number of shortcuts, but normally consist of two parts: a signature in | | followed by a closure body in { }.

Here we will look an extension of this syntax in which the signature is omitted entirely, and some associated syntactic sugar for function calls, which together make idiomatic Fantom using closures look very clean. This is especially useful for declarative programming.

It-Blocks

Where there are no parameters, or one parameter with type inferred, the signature part of a closure is unnecessary. Fantom allows this to be omitted entirely in contexts where a function value of known type is expected.

x.map(|Int n ->Int| { return n*n})   =>
x.map( {it*it} )                     =>
x.map {it*it}

A closure without signature and without any return statement is called an it-block because the missing parameter can be used in the closure body as name it. Thus it-blocks are the ultimate compact way to write closures.

Whenever a method's last parameter expects a closure it can be taken out of the method call brackets and written immediately after the method call. This syntax makes such constructions much more readable. The map method of a List expects a function so this compact notation represents a list of squares. All of the expressions below mean the same thing in Fantom, and represent the list [1,4,9].

 [1,2,3].map {it*it}                     /*preferred idiomatic form */
 [1,2,3].map() {it*it}
 [1,2,3].map( {it*it} )
 [1,2,3].map |n| {n*n}
 [1,2,3].map |Int n| {return n*n}
 [1,2,3].map (|Int n->Int| {return n*n}) /*form with no shortcuts*/

Slot Lookup and Assignment in It-Blocks

Inside an it-block closure the it parameter is used for default lookup of field or method names just like the object in a class:

 [1,2,3].map { negate }  =>   [-1,-2,-3]

The method name negate is looked up as it.negate in this it-block so this negates each list value.

This slot lookup is particularly useful when an it-block is used inside a constructor. In Fantom this is common because many of the API constructors take a closure as optional last parameter of signature |This|. The parameter type-name This here is a special marker which represents the constructed object type and indicates that an it-block closure is expected. Note that in the constructor below f represents the it-block, and this binds to the constructed object. The function call f(this) thus applies the closure to the object.

// default constructor for fwt::Button accepts optional
// function f as parameter
// f is usually a closure written immediately after 
// the constructor call as an it-block
new make(|This|? f := null) { if (f != null) f(this) }

// constructor call with it-block initialisation
Button { size = "100x100" }

If an it-block closure is supplied to the constructor, the closure is called, with its it parameter set to the constructed object, inside the object constructor.

To see why this is useful remember that field and method names inside an it-block are looked up against it. In this case it is the new Button object and so this is a convenient way to initialise fields of a new class instance.

In this example Button.size is set to "100x100".

Inside the constructor the closure is given static permission to set (initialise) const fields on the object. So this is a way to parametrise newly created constant objects. Note that the same operation would not work with the closure called outside the constructor on the object instance, because at that time static const fields cannot be changed.

It-Add Statements (AKA comma operator)

One final tweak added to the it-block syntax is especially useful when it-blocks are used to initalise construction of objects. Any expression in Fantom terminated with a , is transformed into a statement as a parameter to an add method call:

x,   => add(x)

The meaning of this depends on context. The add name is looked up against either the current object instance, inside a class, or the current it paramter, inside an it-block closure.

x,   => it.add(x) 

Idiomatically this allows it-blocks used to initialise constructors that have a number of field assignments, followed by a number of calls to the add method. This is useful in GUI programming as the following examples show.

With-blocks

With-blocks superficially look and behave like it-blocks, but they are semantically very different.

An it-block is a closure appended to a method expecting a closure, possibly to a constructor (typically shortened to a class name). Inside the closure the comma operator appended to an expression translates to an add method call on the constructed object because of the magic of a special constructor that expects a closure as parameter and applies the closure to this inside the constructor. other statements can be used conveniently to set object fields because a name that does not resolve locally will be matched against the closure it parameter which is, because of the form of the constructor, the same as the constructorsthis`.

A with-block is an extension of this paradigm to a more general case, where an it-block closure clos is appended to any expression exp not expecting a closure. When this happens the expression's with method exp.with is implicitly used. The effect of this is to apply the expression to the closure, returning the original expression:

     clos(exp); exp

The with method (on Obj, inherited by every class) is therefore defined:

virtual This with(|This| f) {
  f.call(this)
  return this
}

Parenthetically it is worth noting that because with can be overridden, with-block semantics is customisable on a per-class basis. This allows some interesting non-standard uses of with-block syntax.

For a simple with-block example, incorporating the , add operator:

[1,2] {3,4,} => 
[1,2].with { it.add(3); it.add(4) } => 
[1,2].add(3).add(4)

Here {3,4,} is the with-block. Compare this with:

 List {1,2,3,4,} =>  /* error */

This does not compile because, unusually, the List class does not have any constructor written specially to accept a closure. Also the List class has special syntax for its constructor and no make method, so List on its own is not a valid expression.

The semantic distinction between it-blocks and with-blocks is that an it-block applied to a (closure accepting) constructor will operate on the constructed object inside the constructor and therefore may initialise const fields and objects.

A with-block operates on an already constructed object and therefore cannot change const fields.

Syntactically the two forms are identical.

    ClassName  { <it-block contents> }

The ClassName written on its own will, if possible, result in an implicit call to its constructor. The semantics then depends on whether this accepts a closure parameter, or whether it does not.

Declarative Programming Examples

Here is how to define a top-level Window widget, containing three Button widgets:

class Main
{  
  Void main()
  {
    Window
    {
      it.title = "two Buttons"
      it.size = Size(100,200)
      Button { text = "A"},
      Button { text = "B"},
      Button { text = "C"}
    }.open
  }
}

The Window widget is initialised with an it-block that also uses it-add to add three Button widgets to the window. The add method is invoked by appending the , operator to the relevant expression (the Buttons). Note that as a convenience the last expression in the block is assumed it-add and does not require a ,.

Each Button widget is itself initialised on construction with a nested it-block that sets the text field of the widget.

The open method of the top-level window starts the GUI event loop.

User classes can be used in the same fashion as this example from the standard Fantom desktop fwt example shows:

class DesktopDemo : Canvas
{  
  Void main()
  {
    Window
    {
      it.title = "Desktop Demo"
      it.size = Size(600,400)
      DesktopDemo {},
    }.open
  }

    /* custom onPaint method for DesktopDemo not shown */
}

This function constructs a Window GUI widget and then calls its open method which has the effect of starting the GUI event loop.

The it-block that constructs the Window initialises fields title and size on the Window object and then calls the add method of Window to add the constructed object of type DesktopDemo to the newly created window. The DesktopDemo object is sub-classed from Canvas so this will add a Canvas widget to the window. Note that the it-block that initialises DesktopDemo (whose constructor is inherited from Canvas) in this case contains no further initialisation. This style can easily be used to add nested sub-widgets as is needed, either from the standard API or user-defined.

Summary

Closures can be written compactly in Fantom as it-blocks. This notation combines with syntactic sugar that allows a last function parameter to be pulled out of a function call's brackets and written without brackets after the function. It allows clean code when used with many API functions that accept functions as parameters.

Widgets, and many other Fantom API classes, have constructors that accept an optional last parameter that is a closure called from inside the constructor. That allows the closure to initialise object fields, even const fields.

Inside an it-block an expression terminated by , is translated into a call of the add method on the it parameter.

GUI code typically consists of nested make and add calls with initialisation and benefits from this notation. Closures are written using field assignment to initialise each widget, and , after sub-widgets constructors - themselves using it-blocks - to add sub-widgets.

Any expression not expecting a closure, with it-block appended, forms a with-block. The with-block closure is called with the expression as parameter. This is a post-construction equivalent of an it-block appended to constructor.

Updated by Hertz about 3 years (view history)
12 - Functional Programming and Closures 14 - Facets

Contents

    Welcome to Programming Language Fantom 1.0! 1 - Introduction 2 - Setting Up The Environment 3 - Exploring Hello World 4 - Statements Expressions and Operators 5 - Variables 6 - Control Flow Statements 7 - Types 8 - Classes Part 1 9 - Classes Part 2 10 - Mixins 11 - Collections 12 - Functional Programming and Closures 13 - Declarative Programming and It Blocks 14 - Facets 15 - Serialization 16 - Concurrency Chapter Outline Text Output Show all
    Discussions 0 Pending changes 1 Contributors
    Download Share

    Download

    Working...

    Downloading...

    Downloaded!

    Download more

    Error!

    Your download couldn't be processed. Check for abnormalities and incorrect syntax. We've been notified of the issue.

    Back

    Download PDF Download ePub Download HTML Download Word doc Download text Download source (archive)

    Close
1,541 Words
10,334 Characters

Share

Collaborators make changes on their own versions, and all changes will be approved before merged into the main version.

Close

Penflip is made by Loren Burton
in Los Angeles, California

Tweet

    About

  • Team
  • Pricing
  • Our Story

    Quick Start

  • Markdown
  • Penflip Basics
  • Working Offline

    Support

  • Help
  • Feedback
  • Terms & Privacy

    Connect

  • Email
  • Twitter