Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace nested pattern matching with "do" for Monad

Tags:

haskell

I want to simplify this code

{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson
import Network.HTTP.Types
import Data.Text

getJSON :: String -> IO (Either String Value)
getJSON url = eitherDecode <$> simpleHttp url

--------------------------------------------------------------------
maybeJson <- getJSON "abc.com"
case maybeJson of
  Right jsonValue -> case jsonValue of
      (Object jsonObject) -> 
        case (HashMap.lookup "key123" jsonObject) of
          (Just (String val)) -> Data.Text.IO.putStrLn val
          _ -> error "Couldn't get the key"

      _ -> error "Unexpected JSON"
  Left errorMsg -> error $ "Error in parsing: " ++ errorMsg

by using do syntax for Monad

maybeJson <- getJSON "abc.com/123"
let toPrint = do 
                Right jsonValue <- maybeJson
                Object jsonObject <- jsonValue
                Just (String val) <- HashMap.lookup "key123" jsonObject
                return val
case toPrint of
  Just a -> Data.Text.IO.putStrLn a
  _ -> error "Unexpected JSON"

And it gave me 3 errors:

src/Main.hs:86:19:
    Couldn't match expected type `Value'
                with actual type `Either t0 (Either String Value)'
    In the pattern: Right jsonValue
    In a stmt of a 'do' block: Right jsonValue <- maybeJson


src/Main.hs:88:19:
    Couldn't match expected type `Value' with actual type `Maybe Value'
    In the pattern: Just (String val)
    In a stmt of a 'do' block:
      Just (String val) <- HashMap.lookup "key123" jsonObject


src/Main.hs:88:40:
    Couldn't match type `Maybe' with `Either String'
    Expected type: Either String Value
      Actual type: Maybe Value

Even when I replace 

    Just (String val) <- HashMap.lookup "key123" jsonObject

with

String val <- HashMap.lookup "key123" jsonObject

I'm getting another similar error about Either:

Couldn't match type `Maybe' with `Either String'
    Expected type: Either String Value
      Actual type: Maybe Value
    In the return type of a call of `HashMap.lookup'
    In a stmt of a 'do' block:
      String val <- HashMap.lookup "key123" jsonObject

How do I fix those errors?

like image 737
Incerteza Avatar asked Sep 30 '22 10:09

Incerteza


1 Answers

You can't easily simplify that into a single block of do-notation, because each case is matching over a different type. The first is unpacking an either, the second a Value and the third a Maybe. Do notation works by threading everything together through a single type, so it's not directly applicable here.

You could convert all the cases to use the same monad and then write it all out in a do-block. For example, you could have helper functions that do the second and third pattern match and produce an appropriate Either. However, this wouldn't be very different to what you have now!

In fact, if I was going for this approach, I'd just be content to extract the two inner matches into their own where variables and leave it at that. Trying to put the whole thing together into one monad just confuses the issue; it's just not the right abstraction here.

Instead, you can reach for a different sort of abstraction. In particular, consider using the lens library which has prisms for working with nested pattern matches like this. It even supports aeson nateively! Your desired function would look something like this:

decode :: String -> Maybe Value
decode json = json ^? key "key123"

You could also combine this with more specific prisms, like if you're expecting a string value:

decode :: String -> Maybe String
decode json = json ^? key "key123" . _String

This takes care of parsing the json, making sure that it's an object and getting whatever's at the specified key. The only problem is that it doesn't give you a useful error message about why it failed; unfortunately, I'm not good enough with lens to understand how to fix that (if it's possible at all).

like image 92
Tikhon Jelvis Avatar answered Nov 15 '22 08:11

Tikhon Jelvis