Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
NOT FOR HUMAN CONSUMPTION.
branch: master

README.markdown

What is Foam?

Foam is a small programming language that compiles directly to Objective-C. It is mainly influenced by the following:

Foam is written in Haskell using Parsec. The beauty of using parser combinators is that the entire grammar is expressed in the host language, as opposed to a special language which must be compiled into an actual parser (like the various BNF-based approaches).

What does it add to Objective-C?

Foam has several benefits over using raw Objective-C:

Single-file modules and category visibility annotations

Foam is built around classes and categories, just like Objective-C. Categories can be annotated as follows to determine which way they appear (or don't) in the public interface:

  1. @public: a category interface with all its methods appears in the header file. (Use this for organizing separate aspects of a public API.)

  2. @private: a category interface will all its methods appears in the .m file. (Use this for a private interface).

  3. @override: no category interface appears. (Use this for overriding boilerplate methods from a superclass, like -[UIViewController viewDidLoad]).

Furthermore, a shortcut is given for categories which are specifically meant to implement a protocol for a class. Whereas in Objective-C, one must declare a name for the category (such as TextFieldDelegate) and declare adoption of the protocol (UITextFieldDelegate), the name is unnecessary in Foam:

category FMViewController <UITextFieldDelegate> [
  - textFieldShouldReturn:field [
    field resignFirstResponder.
    ^ %no.
  ]
]

This will be compiled into a category FMViewController (UITextFieldDelegate) <UITextFieldDelegate>.

Built-in Objective-C Literals

Let's say that you need to make an array of numbers 1-4. In Objective-C, you would probably do the following:

id array = [NSArray arrayWithObjects:
            [NSNumber numberWithInt:1],
            [NSNumber numberWithInt:2],
            [NSNumber numberWithInt:3],
            [NSNumber numberWithInt:4],
            nil];

In Foam, the same thing can be accomplished much more simply:

array := {1,2,3,4}.

That's because all Foam literals are boxed by default. As for dictionaries:

id dict = [NSDictionary dictionaryWithObjectsAndKeys:
           @"tucker", @"name",
           @"dog", @"species",
           [NSNumber numberWithBool:YES], @"happy",
           nil];

can be expressed in Foam in one of two ways:

dict := { "name" => "tucker", "species" => "dog", "happy" => yes }.
dict := { name: "tucker", species: "dog", happy: yes }.

In Foam, string, number and boolean literals can be explicitly unboxed by prepending the %-sigil:

@navigationController pushViewController:controller animated:%yes.

No bracket-soup.

Brackets don't proliferate in Foam like they do in Objective-C. Take the following contrived example:

id cell = [[[[controller classForCellAtIndexPath:p] alloc] initWithFrame:CGRectZero style:UITableViewCellStyleDefault] autorelease];

In Foam, that turns into the following:

cell := controller classForCellAtIndexPath:p alloc initWithFrame:CGRectZero style:UITableViewCellStyleDefault autorelease.

That was sort of convenient, since several keyword messages were delimited by unary messages, preventing ambiguities. But what about more ambiguous situations:

[[Blah usingString:@"doggy" atIndex:3] configureForView:[self view] animated:NO];
id bowtieData = [bowties objectAtIndex:[indexPath row]];

can be expressed in Foam as follows:

Blah usingString:"doggy" atIndex:%3 | configureForView:@view animated:%no.
bowtieData := bowties objectAtIndex: (indexPath row).
bowtieData := bowties objectAtIndex: $ indexPath row.

Note that the $-operator simply forces the rest of the current line to associate (rather like in Haskell, although this is hard-coded into Foam's syntax).

The @-sigil

In Ruby, the @-sigil is used to access instance variables. In CoffeeScript, it is a shortcut for this.*; Foam uses this latter approach. It can be used to access properties of self, as well as arbitrary methods:

id controller = [self createSettingsControllerWithData:data];
[self presentModalViewController:controller animated:YES];

can be expressed as

controller := @createSettingsControllerWithData:data.
@presentModalViewController:controller animated:%yes.

Operators for Objects and Primitives

In Foam, you can use familiar operators with both primitives and objects. The way this works that operators are compiled down to invocations of overloaded functions to be provided in a standard library; for instance, a * b compiles to foam::times(a,b). Then, depending upon the static types of a and b, this turns out to either invoke C's * operator, or send the -fm_times: message to a with b as its argument.

There are a few special operators that are reserved for objects. These are <$> (for mapping over Functors), <~ (for left-catamorphisms), and ~> (for right-catamorphisms):

arr := {1,2,3} <$> [:x | ^ x + 1. ].
# => {2,3,4}

dog := { name : "tucker", activity: "barking" }.
loudDOG := dog <$> [:k :v | ^ { k => v uppercaseString }. ].
# => {name: "TUCKER", activity: "BARKING"}.

income := { 98000, 103000, 102430, 95480 }.
total := income <~ [:m :v | ^ m + v. ].
# => 398910

Optional Typing and Limited Type Inference

Foam compiles to Objective-C++, and as such makes use of C++11's local type inference. In this way, declarations of both objects and primitive values work without any additional type annotations. If you wish to override this behavior, you can provide a type annotation in braces following the variable's name:

implicit := %yes.
# => auto implicit = YES;
explicit{BOOL} := %yes.
# => BOOL explicit = YES;

This behavior allows for a sort of best of both worlds, in that you will still receive compiler warnings (on the Objective-C++ output) if you, for instance, send an unsupported message to an object:

dict := {"this","is","an","array","not","a","dictionary"}.
compileTimeError := dict objectForKey:"oops".
# Your Objective-C compiler will warn you that `dict` is an NSArray,
# and does not respond to -objectForKey:.

Type Inference for Methods

In variable declarations, type annotations occur after the variable name; in method declarations, it is the opposite:

- methodWhichTakesInteger:{NSInteger}i [ ]

In some circumstances, it is optional to declare the return type of a method. The Foam compiler actually checks through a method's implementation, looking for return statements. If one of the following is true, the return type can be inferred:

  1. There is no return statement: void.

  2. Your method returns literal value (string, number, boolean, etc.), boxed or unboxed.

  3. Your method returns an object: id.

In all other cases, you must declare the return type of your method. So, the following will work:

- exampleVoid [ ]

- exampleUnboxedStringLiteral [
  ^ %"abcdef".
]

- exampleNumber [
  ^ 3.
]

- exampleObject [
  ^ NSObject new.
]

- {int}uninferrableExample [
  i := %134.
  ^ i.
]
- (void)exampleVoid {}

- (const char *)exampleUnboxedStringLiteral {
  return "abcdef";
}

- (NSNumber *)exampleNumber {
  return [NSNumber numberWithInt:3];
}

- (id)exampleObject {
  return [NSObject new];
}

- (int)uninferrableExample {
  auto i = 123;
  return i;
}

Blocks

Foam supports Objective-C blocks using a Smalltalk-like syntax. Do not be deceived by how much it looks like Smalltalk, however: whilst a ^ (return) statement within a block would cause a return in the calling environment in Smalltalk, Foam aligns with Objective-C in its return-from-block behavior. Unlike in Smalltalk, there is no way to force the calling environment to return from within a block, just as in Objective-C. That said, here's what it looks like:

object afterDelay:%1.0 performBlock: [:obj | obj fire. ].

This would be expressed in Objective-C as:

[object afterDelay:1.0 performBlock:^(id obj) { [obj fire]; }];

Block argument types are id by default; they can be specified using annotations:

integerIdentity := [:i{NSInteger} | ^ i. ].

Lastly, Foam uses a much cleaner syntax for block types. For instance, let us consider the type of a function that takes a controller object, a string, and a continuation for errors, and then returns a dictionary. In Objective-C, this could look like the following:

- (void)setHandler:(NSDictionary*(^)(JSController*,NSString*,(void(^)(NSError*)))block;

That's basically incomprehensible. In Foam, however, it's pretty easy to tell what's going on:

- setHandler:{JSController*,NSString*,(NSError* => void) => NSDictionary*}block.

Additionally, with this syntax, lazy parameters can be expressed very idiomatically, as in Scala:

- useIntegerOnDemand:{=> NSInteger}intByNeed [
  @doSomething: intByNeed().
].

Let vs. Assign

You may be wondering how to reassign a variable, since the :=-operator seems to always create a new one. That's because Foam has separate let-statements (using :=) and assignment-statements (using =). Hence:

var := "dog".
var = "cat".

will compile to

auto var = @"dog";
var = @"cat";

What does it look like?

Here's an example of the current Foam syntax:

import <MediaPlayer/MediaPlayer.h>.

# Comments can be fun. Foam only has single-line comments, marked by `#`.
# The Foam parser strips out any comments before compilation.

class PNPlaylistsViewController << PNTableViewController [
  property{strong} playlists{NSArray*}.
]

category PNPlaylistsViewController (UIViewController) @override [
  - viewDidLoad [
    super viewDidLoad.
    @title = "playlists".

    @do: @biscuits with: @object.
    query := MPMediaQuery playlistsQuery.
    @playlists = query collections.
  ]
]

category PNPlaylistsViewController <UITableViewDataSource> [
  - {NSInteger}tableView:table numberOfRowsInSection:{NSInteger}section [
    ^ @playlists count.
  ]

  - configureCell:cell forIndexPath:path [
    item := @playlists objectAtIndex: $ path row.
    cell textLabel text = item valueForKey: MPMediaPlaylistPropertyName.
    cell accessoryType = UITableViewCellAccessoryDisclosureIndicator.
  ]

  - tableView:table cellForRowAtIndexPath:path [
    cell := table dequeueReusableCellWithIdentifier: "PlaylistCell".
    @configureCell: cell forIndexPath: path.
    ^ cell.
  ]
]

category PNPlaylistsViewController <UITableViewDelegate> [
  - tableView:table didSelectRowAtIndexPath:path [
    super tableView:table didSelectRowAtIndexPath:path.

    controller := PNSongsViewController new.
    controller playlist = @playlists objectAtIndexPath: $ path row.

    PNMusicController instance setQueueWithItemCollection: $ controller playlist.
    @navigationController pushViewController:controller animated: %no.
  ]
]

Should I use it?

Yes! But not just yet. There are a few things missing and I've not yet finalized certain aspects of it yet.

Something went wrong with that request. Please try again.