Better Living Through Concurrency

Concurrency in Fate is accomplished via Continuations. Essentially, any code that is executed within a do block is potentially asynchronous. To retrieve the value of the block's calculation, one has to await the result. This can be accomplished either with an await expression or a when clause added to do block.

do

The do keyword kicks off an asynchronous calculation. The Fate parser will sequence everything between the do keyword and its closing end. The result of such a block is a Continuation that can be passed to the await operator. The do block comes in three flavors: do when, do case, and the plain old do.

Plain old "do"

from io import print

let getHomePage = do
  # perform some asynchronous work
  http.get('http://www.example.org')
end

do
  await getHomePage() | print
end

do when

do can also be called with a when clause. This clause assigns the eventual results of asynchronous calculations to variables scoped within the block.

do when content = http.get('http://www.example.org'), 
        {name, status} = getUser()
  # content, name, and status will have already been resolved
end

Wherever possible, the assignments of a when clause will be performed in parallel. It is only when one assignment depends on the result of a previous one that the calculations happen serially.

do when a = timeout(10), b = timeout(a * 5), c = a * 50, d = b * 5, e = 100
  # above assignments are performed in three groups: [a, e], [b, c], [d]

  a | print   # 10
  b | print   # 50
  c | print   # 500
  d | print   # 250
  e | print   # 100
end

There are five assignments in the example above. When parsing, Fate will recognize that b and c depend on a. It will also see that d depends on b. But a and e depend on nothing. This means that Fate can perform the resolution in three steps. The first step will resolve a and e in parallel. The second will resolve b and c in parallel, and the final step will resolve d. And you didn’t have to think about it.

The when clause can also accept a single expression rather than a set of assignments.

do when timeout(5000)
  print("five seconds later")
end

do case

do with a nested series of case clauses allows you to perform multiple simultaneous resolutions, and execute only the block for the first resolved case. Like the when clause, each case clause can either accept a set of assignments or a single expression.

do
  case x = timeout(10)
    x | '% won' | print

  case x = timeout(5)
    x | '% won' | print

  case timeout(20)
    print('whole thing timed out')
end

In this case, the only thing that will be displayed is "5 won".

await

Must appear within a do block. When an await expression is encountered, control is passed to the asynchronous sequencer. Then, when the expression specified by await can be resolved, it will trigger a continuation of the do block.

let content = await http.get('http://www.example.org')

await any

A standard await will only wait for a single calculation to be processed. The await any modifier allows the language to monitor an array of do blocks for completion. It will then return the first completed result.

let pages = [
  'http://www.fate-lang.org',
  'http://www.example.org',
  'http://www.microsoft.com'
]

let requests = [for x in pages select x | http.get]

do: await any requests | print

await all

Like await any, the await all modifier allows the language to monitor an array of do blocks for completion. In this case, it will wait for all to complete, and then return their results as an Array.

let pages = [
  'http://www.fate-lang.org',
  'http://www.example.org',
  'http://www.microsoft.com'
]

let requests = [for x in pages select x | http.get]

do
  for content in await all requests
    content | print
  end
end

awaiting right calls

Now that you've learned about the await keyword, you may be thinking "but won't my code become a mess of 'awaits'?" Depending on what you're doing, it's quite possible. That's why Fate also includes a version of the right-call operator that allows you to easily chain the resolution of Continuations into function invocations.

import io

let double = (arr -> do: [for a in arr select a * 2])
let timeouts = (arr -> [for a in arr select io.timeout(a)])

do
  [100, 200, 300] | timeouts :| double .| io.print
end

The example above is passing an array into a function that creates three separate timeout Continuations using the timeouts lambda. The result of each element is awaited, and the resolved array is then passed into the double lambda. The result of that function is then awaited and passed into io.print.

Like the await keyword, the awaiting right call operator also supports modifiers.

.| - Normal 'await' - Calls the right function with the resolved left expression
?| - 'await any' - Calls the right function with the first resolved array element
:| - 'await all' - Calls the right function with the resolved array elements

results matching ""

    No results matching ""