It's hard to find docs and examples of Async lib

OCaml is a great language with its type system and module system. But I have to say that it’s nightmare to find some docs or examples some very base usage.

For example, I want to write a simple udp server, after google it, I got an example from SO

open Core
open Async

let wait_for_datagram ()  =
  let port = 9999 in
  let addr = Socket.Address.Inet.create Unix.Inet_addr.localhost ~port in
  let%bind socket = Udp.bind addr in
  let socket = Socket.fd socket in
  let stop = never () in
  let config = Udp.Config.create ~stop () in
  let callback buf _ : unit = failwith "got a datagram" in
  Udp.recvfrom_loop ~config socket callback

But it’s error on let%bind socket = Udp.bind addr in as follows:

Error: This expression has type
         ([ `Bound ], Async_extra__.Import.Socket.Address.Inet.t)
         Async_extra__.Import.Socket.t =
           ([ `Bound ], Async_extra__.Import.Socket.Address.Inet.t)
           Async_unix__Unix_syscalls.Socket.t
       but an expression was expected of type
         'a Async_kernel__Deferred.t = 'a Async_kernel__Types.Deferred.t

So I have to read the source code of async_extra:

val bind
  :  ?ifname : string
  -> Socket.Address.Inet.t
  -> ([ `Bound ], Socket.Address.Inet.t) Socket.t

Yes, bind would return a [Bound],..., but why the guys from SO works?

It just cost me a few minutes to find a Python UDP server example:

import socket
import sys

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

server_address = ('localhost', 10000)
print >>sys.stderr, 'starting up on %s port %s' % server_address
sock.bind(server_address)

while True:
    print >>sys.stderr, '\nwaiting to receive message'
    data, address = sock.recvfrom(4096)
    
    print >>sys.stderr, 'received %s bytes from %s' % (len(data), address)
    print >>sys.stderr, data
    
    if data:
        sent = sock.sendto(data, address)
        print >>sys.stderr, 'sent %s bytes back to %s' % (sent, address)

It would be better to add some more basic example on GitHub or some wiki else.

I can’t help with the lack of examples for async, but your concrete problem here is solved by paying a little more attention to the error message. let%bind lhs = rhs is something that works only when rhs is of type Deferred.t In your case, the type checker informs you that it’s Async_unix__Unix_syscalls.Socket.t You can fix your program by normally binding the result Udp.bind:

open Core
open Async

let wait_for_datagram ()  =
  let port = 9999 in
  let addr = Socket.Address.Inet.create Unix.Inet_addr.localhost ~port in
  let socket = Udp.bind addr in
  let socket = Socket.fd socket in
  let stop = never () in
  let config = Udp.Config.create ~stop () in
  let callback buf _ : unit = failwith "got a datagram" in
  Udp.recvfrom_loop ~config socket callback

Btw, how did you try running the code? It’s surprising that the error message is so ugly with those __ everywhere.

2 Likes

As an aside, if you don’t have a particular reason to use Async you might be better off with Lwt. It has much nicer documentation and it’s used more than Async in the community (outside of Jane Street), so you should be able to find examples more easily.

Thanks for your reply, but actually I couldn’t find any docs or examples about lwt udp server

The only example works was neither LWT nor Async.

(* A dummy UDP server *)

let maxlen = 1024
let portno = 8125

let sock =
  Unix.socket Unix.PF_INET Unix.SOCK_DGRAM
    (Unix.getprotobyname "udp").Unix.p_proto

let () =
  Unix.bind sock (Unix.ADDR_INET (Unix.inet_addr_any, portno));
  Printf.printf "Awaiting UDP messages on port %d\n%!" portno

let oldmsg = ref "This is the starting message."

let () =
  let newmsg, response = String.create maxlen, "Thanks bro" in
  while true do
    let newmsg, hishost, sockaddr =
      match Unix.recvfrom sock newmsg 0 maxlen [] with
        | len, (Unix.ADDR_INET (addr, port) as sockaddr) ->
            String.sub newmsg 0 len,
            (Unix.gethostbyaddr addr).Unix.h_name,
            sockaddr
        | _ -> assert false in
    Printf.printf "Client %s said ``%s''\n%!" hishost newmsg;
    ignore
      (Unix.sendto sock response 0 (String.length response) [] sockaddr);
  done

What I’m trying to say is in addition to the lack of documentation, the API of Async seems keep changing all the time.

The example from SO has syntax error:

  let%bind socket = Udp.bind addr in 

Another example from discuss.ocaml.org also has syntax error right now:

let () = 
  Command.async
    ~summary:"herp"
    Command.Spec.(empty)
    (fun () -> def)
  |> Command.run


File "sendudp.ml", line 23, characters 4-19:
Error: The function applied to this argument has type
         ?extract_exn:bool -> ?readme:(unit -> string) -> Command.t
This argument cannot be applied without label

Just to be clear about this, these errors are just type errors due to the API changes you mentioned. There are no issues with the syntax in any of these examples.

As for the issue of not having any up to date examples, I agree that the current situation is tough and wastes a lot of time for people who are trying to get started.

Maybe try accomplishing your goals with Lwt? Echoing the other commenter, I also feel that Lwt is better documented. So you should have an easier time getting started with it.

This is perhaps small consolation, but the change that’s biting you here is a change in the Command library, not in Async. There are two styles of Command declarations, and the one that example is using is the older style. You can invoke that style explicitly, as follows:

let () = 
  Command.async_spec
    ~summary:"herp"
    Command.Spec.(empty)
    (fun () -> def)
  |> Command.run;;

There are some fully-worked out (and compiling) examples in the Async chapter of RWO, which you can find here:

https://dev.realworldocaml.org/concurrent-programming.html

Hope that helps.

y

And this chapter will help you out if you want a guide to using the Command library.

https://dev.realworldocaml.org/command-line-parsing.html

And you can find some examples of Command in action here:

And some examples for Async here:

As well as some more end-to-end examples in this repo:

And, while there’s much to do to improve the doc generation process and to create more examples, Async’s docs have lots of useful info, and are available here:

https://ocaml.janestreet.com/ocaml-core/latest/doc/async/index.html

Which you can also find if you start at our opensource site:

Happy hunting!
y

This has to do with the lack of short-paths. One day, we need to get short paths on by default at the compiler itself, but for now, you can turn it in by adding this to your jbuild:

(flags (:standard -short-paths))

At which point, you get this error message:

Error: This expression has type ([ `Bound ], Socket.Address.Inet.t) Socket.t
       but an expression was expected of type 'a Deferred.t

which is a lot easier to parse.

4 Likes

Somehow I never knew about this, and I am so happy. Going to patch all my jbuild files now.

Maybe everyone already knew about this except me, but I highly recommend putting it somewhere in a “best practices” doc or something.

1 Like

I wonder if this should just be added to the list of standard flags in Dune.

@jeremiedimino @rgrinberg, what do you think?

y

5 Likes

Elsewhere, @rgrinberg pointed out that when you pass --dev to jbuilder, this flag gets added automatically. Maybe we could just make --dev the default. Apparently that’s what Rust does, and it seems like it would provide a better user experience all around.

y

10 Likes

Adding -short-paths by default seems good but I think we should flip the default in the compiler first for consistency.

Making dev the default seems good to me. We could do this change as part of the renaming to dune, since it’s technically a breaking change.

6 Likes

Done in https://github.com/ocaml/dune/pull/920

4 Likes