wreedb
wreedb

Reputation: 13

a simple question about a simple http get request of a json string in haskell.. :(

I'm trying to learn haskell, and what better a way than to learn by converting an already existing program I have made over to haskell, since I know how my program works otherwise.

the first step is to make a simple http get request to a link that provides me a JSON string. I have been digging, and dumpster diving through as much haskell documentation as I can, but I am getting the idea that haskell documentation is obviously... not accessible to non-haskell programmers.

I need to take a link- lets say https://aur.archlinux.org/rpc/?v=5&type=search&by=name-desc&arg=brave

and make a get request on that link. In other languages, there seemed to be an easy way to get the body of that response as a STRING. In haskell im wracking my brain with bytestrings? and "you cant do this because main is IO" and etc etc.

I just can't make any sense of it and I want to breach through this accessibility barrier that haskell has because I otherwise love functional programming, I dont wanna program another way!

{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Simple

import qualified Data.ByteString.Char8 as B8

main :: IO ()
main = do
  httpBS "https://aur.archlinux.org/rpc/?v=5&type=search&by=name-desc&arg=brave" >>= B8.putStrLn . getResponseBody

Doing this gets the response and outputs it as standard out, but I need to save this as a string, or convert it from a bytestring? to a string so that I can parse it.

If I sound defeated it's because I very much am.

Upvotes: 1

Views: 534

Answers (1)

Thomas M. DuBuisson
Thomas M. DuBuisson

Reputation: 64740

what better a way than to learn by converting an already existing program I have made over to haskell, since I know how my program works otherwise.

This strategy will encourage you to carry over idioms from the original language. I'm guessing your prior language didn't encourage use of monads and wasn't lazy. Perhaps it wasn't even functional, but the point is this can actually be a detrimental start.

I just can't make any sense of it and I want to breach through this accessibility barrier

Learning to read documentation is hard in any unknown language (for me at least). Understanding each character is actually rather important. For example, a ByteString - an array of bytes, or just Bytes if it were better named - isn't a hard concept but the name implies things to people.

If I sound defeated it's because I very much am.

You're so close! Think of the high level:

  1. Get the data with an HTTP GET
  2. Parse the data from the string (or bytes) into a structure. Python uses a dictionary, for example.

You did 1 already, nice work. Rather than do everything in a single line in point free style (composing a bunch of functions with a bunch of operators), let's name our intermediate values and have one concept per line:

#!/usr/bin/env cabal
{- cabal:
    build-depends: base, http-conduit, aeson
-}
{-# LANGUAGE OverloadedStrings #-}

I'm using a shebang so I can just chmod +x file.hs and ./file.hs as I develop.

import Network.HTTP.Simple
import qualified Data.Aeson as Aeson

The most common JSON library in Haskell is Aeson, we'll use that to parse the bytes into json much like python's json.loads.

main :: IO ()
main = do
  httpRequest <- parseRequest "https://aur.archlinux.org/rpc/?v=5&type=search&by=name-desc&arg=brave"
  response <- httpLBS httpRequest

You're start is good. Notice httpBS is a class of functions generically http<SomeTypeOfResult> and not httpGet like you might have seen in Java or Python. The function learns if this is a GET (vs POST etc) and the headers using fields in the Request data type. To get a Request we parse the URL string and just use all of parseRequests defaults (which is HTTP GET).

I did change to getting lazy byte strings (LBS) because I know the Aeson library uses those later on. This is something like an iterator that produces bytestrings (for intuition, not entirely accurate).

Rather than >>= moreFunctions I'm naming the intermediate value response so we can use it and look at each step separately.

  let body = getResponseBody response

Extracting the body from the response is just like what you had, except as a separate expression.

  let obj = Aeson.decode body :: Maybe Aeson.Object

The big part is to decode the bytes to JSON. This is hopefully familiar since every language under the sun does json decoding to some sort of dictionary/map/object. In Haskell you'll find it less common to decode to a map and more common to define a structure that is explicit in what you expect to have in the JSON then make a custom decoding routine for that type using the FromJSON class - you don't have to do that, it brings in way more concepts than you'll want when just getting started as a beginner.

  print obj

I know this doesn't need explained.

Alternative

If you saw the documentation you might have seen (or considered searching the page for) JSON. This can save you lots of time!

#!/usr/bin/env cabal
{- cabal:
    build-depends: base, http-conduit, aeson
-}
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Simple
import qualified Data.Aeson as Aeson

main :: IO ()
main = do
  response <- httpJSON =<< parseRequest "https://aur.archlinux.org/rpc/?v=5&type=search&by=name-desc&arg=brave"
  let obj = getResponseBody response :: Maybe Aeson.Object
  print obj

Upvotes: 4

Related Questions