Skip to content

Instantly share code, notes, and snippets.

@evancz
Last active August 29, 2015 14:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save evancz/217012b714dc4e831735 to your computer and use it in GitHub Desktop.
Save evancz/217012b714dc4e831735 to your computer and use it in GitHub Desktop.

Async/Await Syntax

This is a specialization of the initial proposal that only works on tasks. It introduces the keywords async and await.

  async
    let
      _ = await print 3
      _ = await print 4
    in
      await print 5

The idea is that you can put the await keyword anywhere under an async, as long as you do not cross into a closure. Here are some more examples.

getPeople : Task Http.Error (List Person)
getPeople =
  async
    let
      olds = await get (list person) oldPeopleUrl
      youngs = await get (list person) youngPeopleUrl
    in
      olds ++ youngs


getPeople' : Task Http.Error (List Person)
getPeople' =
  async
    await get (list person) oldPeopleUrl ++ await get (list person) youngPeopleUrl

Positives

  • Exists in C# and is proposed for ES7
  • You can be more flexible about where you put await indicators
  • We can do (mb = await mailbox) in the main module
  • It rules out all the crazy / questionable stuff we were doing with JSON, XML, Maybe, etc.
  • No general purpose thing for now, no need to explicitly think about andThen, andMap, etc.

Negatives

  • The path to making this generic is much trickier
  • We do not cover the Haxl case

Long-term prospects

I think my biggest concern here is how we'll support Haxl, or other clever things that might come along. Maybe we could have something like the "programmable let" thing like this:

  @async haxl
    await get (list person) oldPeopleUrl
      ++ await get (list person) youngPeopleUrl

And that'd do the same sort of rewriting we were talking about with "programmable let". The trouble is that it looks sort of goofy for things that are not asynchronous.

add : Maybe Int -> Maybe Int -> Maybe Int
add mx my =
  @async maybe
    await mx + await my

Maybe that is fine? Maybe it'll be easier to cross this bridge once tons of people understand async/await?

Subtlties

One crucial detail is that if you define a function or a lambda, you cannot use await in the body unless it also is paired with another async. So here's something that is NOT allowed:

-- THIS DOES NOT WORK!!!
getPeople : Task Http.Error (List PersonSummary)
getPeople =
  async
    let
      summary person =
          await get summary (urlFor person)

      olds = await get (list person) oldPeopleUrl

      youngs = await get (list person) youngPeopleUrl
    in
      map summary (olds ++ youngs)

I don't think it is possible to make the types work out here. Here are two fixed versions.

getPeople : Task Http.Error (List PersonSummary)
getPeople =
  async
    let
      summary person =
          async
            await get summary (urlFor person)

      olds = await get (list person) oldPeopleUrl

      youngs = await get (list person) youngPeopleUrl
    in
      await sequence (map summary (olds ++ youngs))


-- it is redundant to write "async await" right next to each other
-- so we can simplify to this
getPeople : Task Http.Error (List PersonSummary)
getPeople =
  async
    let
      summary person =
          get summary (urlFor person)

      olds = await get (list person) oldPeopleUrl

      youngs = await get (list person) youngPeopleUrl
    in
      await sequence (map summary (olds ++ youngs))
getImage : (Int,Int) -> String -> Task Http.Error String
getImage dimensions tag =
async
let
searchUrl =
flickrUrl "search" [ ("sort", "random"), ("per_page", "10"), ("tags", tag) ]
photos =
await Http.get photoList searchUrl
photo =
await Task.fromMaybe photoErr (List.head photos)
sizes =
await Http.get sizeList (flickrUrl "getSizes" [ ("photo_id", photo.id) ])
pickSize =
sizes
|> List.sortBy (sizeRating dimensions)
|> List.head
|> Task.fromMaybe sizeErr
in
await pickSize
field : String -> JS.Decoder a -> JS.Decoder a
index : Int -> JS.Decoder a -> JS.Decoder a
person : Decoder Person
person =
let position =
JS.map2 (,) (index 0 JS.float) (index 1 JS.float)
in
JS.map3 Person ("name" => JS.string) ("age" => JS.int) ("position" => position)
add : Maybe Int -> Maybe Int -> Maybe Int
add maybeLeft maybeRight =
Maybe.map2 (+) maybeLeft maybeRight
person : Generator Person
person =
Gen.map3 Person
Gen.string
Gen.int
(Gen.map2 Location Gen.int Gen.int)
type alias Person =
{ fullname : String
, age : Int
, location : Location
}
type alias Location =
{ longitude : Int
, latitude : Int
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment