ocaml

This expression has type unit but an expression was expected of type 'a Client.io


I am trying to write a simple executable code in OCaml below.

open Printf
open Lwt
open Cohttp
open Cohttp_lwt_unix
open Yojson

let () =
  let ip = "8.8.8.8" in
  let key = "" in
  let uri =
    Uri.of_string
      ("https://api.ip2location.io/?format=json&key=" ^ key ^ "&ip=" ^ ip)
  in
  Lwt_main.run
    ( Client.get uri >>= fun (resp, body) ->
      let code = resp |> Response.status |> Code.code_of_status in
      let json_promise = body |> Cohttp_lwt.Body.to_string in
      json_promise >>= fun json_string ->
      let json = Basic.from_string json_string in
      let open Yojson.Basic.Util in
      if code == 200 then
        if member "usage_type" json <> `Null then
          let usage_type = json |> member "usage_type" |> to_string in
          printf "usage_type: %s\n" usage_type
        else
          printf
            "ERROR: The usage_type field requires a paid subscription to the \
             Starter plan or higher."
      else if (code == 400 || code == 401) && member "error" json <> `Null then
        let error_message =
          json |> member "error" |> member "error_message" |> to_string
        in
        printf "ERROR: " ^ error_message
      else printf "HTTP Code: " ^ Int.to_string code )

But I keep seeing the below when I run dune build.

File "bin/main.ml", line 24, characters 10-46:
24 |           printf "usage_type: %s\n" usage_type
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: This expression has type unit but an expression was expected of type
         'a Client.io

From other StackOverflow posts, it seems to be related to the different types being returned in the if/else but I have made sure all of the if/else are using printf.

I would appreciate if anyone can let me know what else I'm doing wrong.


Solution

  • Andreas Rossberg has identified one issue, but you're also seeing some operator precedence issues.

    This kind of thing:

    printf "ERROR: " ^ error_message
    

    And this:

    printf "HTTP Code: " ^ Int.to_string code
    

    Look really suspicious, since printf "HTTP code: " evaluates to () with cannot be provided to ^. It looks like you meant to write something like:

    printf ("HTTP Code: " ^ Int.to_string code)
    

    But you might as well leverage printf to write the following, which is cleaner and has no operator precedence issues.

    printf "HTTP Code: %d" code
    

    Also beware opening several modules. If more than one defines the same operator or they redefine standard library operators you can end up with some unexpected behavior.

    Aside from fully qualifying names, you can locally open modules, mitigating this issue. But since you've already used this once in your code, it seems you're aware of this option.

    On a style note, I might just refactor this:

            let error_message =
              json |> member "error" |> member "error_message" |> to_string
            in
            printf "ERROR: " ^ error_message
    

    To:

            json 
            |> member "error" 
            |> member "error_message" 
            |> to_string
            |> printf "ERROR: %s"
    

    Also opinion, but there is an opportunity to rework:

          if code == 200 then
            if member "usage_type" json <> `Null then
              let usage_type = json |> member "usage_type" |> to_string in
              printf "usage_type: %s\n" usage_type
            else
              printf
                "ERROR: The usage_type field requires a paid subscription to the \
                 Starter plan or higher."
          else if (code == 400 || code == 401) && member "error" json <> > `Null then
            let error_message =
              json |> member "error" |> member "error_message" |> to_string
            in
            printf "ERROR: " ^ error_message
          else printf "HTTP Code: " ^ Int.to_string code )
    

    Using pattern-matching and conditional guards:

          match code with
          | 200 when member "usage_type" json <> `Null ->
            json
            |> member "usage_type" 
            |> to_string
            |> printf "usage_type: %s\n"
          | 200 ->
            printf
              "ERROR: The usage_type field requires a paid subscription to the \
               Starter plan or higher."
          | (400 | 401) when member "error" json <> `Null ->
            json 
            |> member "error" 
            |> member "error_message" 
            |> to_string
            |> printf "ERROR: %s"
          | _ -> printf "HTTP Code: %d" code