0

I'm learning Haskell through writing toy-language. And I wondering how combine let with nested-do constraction. I want to write something like that (completely usual use-case but with State-monad in do-notation):

let x' = if special x then do ... else x

more precisely I want to write someting like this code:

moveOpersToVirt :: [IOper] -> State (RegNum, M.Map OperNum Var, TRegs) [IOper]
moveOpersToVirt ((i, Oper op r arg1 arg2):os) = do
  (virt_rn, mp, regs) <- get
  let arg2' = if isRegGlobal arg2
      -- get previously remembered value for "global reg arg2"
      then getRegStrict regs (getValRegStrict arg2) 
      else arg2
  let arg1' = ...
  let r' = if isRegGlobal r
    -- substitute "global reg r" to "virtual reg r1"
    -- and remember it in State
    then do 
      let virt_rn1 = virt_rn + 1
      let r1 = Var virt_rn1
      let regs1 = setReg regs r r1
      let mp1 = M.insert i r1 mp
      put (virt_rn1 + 1, mp1, regs1)
      r1
    else r
  rest_opers  <- moveOpersToVirt os
  return $ (Oper op r' arg1' arg2'):rest_opers 

I guess I could write something like that:

moveOpersToVirt :: [IOper] -> State (RegNum, M.Map OperNum Var, TRegs) [IOper]
moveOpersToVirt ((i, Oper op r arg1 arg2):os) = do
  (virt_rn, mp, regs) <- get
  let arg2' = useToVirt regs arg2   -- pure function
  let arg1' = useToVirt regs arg2   -- pure function
  r' <- moveDefToVirt r             -- function in the same State Monad
  rest_opers <- moveOpersToVirt os
  return $ (Oper op r' arg1' arg2'):rest_opers

but:

  • I really wonder how write some "unbalanced" nested do.
  • moveDefToVirt have the same problem (less sharp due to the smaller size of function): we need state monad only in "then" branch and not in else
  • 1
    I'm not sure if I understand your question correctly, but I think you should be able to wrap the else expression into a monad with `else return r`, and then instead of using `let r' =`, chain the result with `r' <-` – ShamPooSham Jan 12 '22 at 22:45
  • Is the return value `[IOOper]` or `[Oper]`?. Your argument is a tuple, but the return value is just a list of values of the tuple's second type. – chepner Jan 13 '22 at 15:21
  • Consider writing a helper function `f` that operates on a single `IOPer` value, then defining `moveOpersToVirt = traverse f`. – chepner Jan 13 '22 at 15:22

1 Answers1

6

The two branches of an if must have the same type. If you need one with a monadic type and the other with a "pure" type, you need to make those types equal using a return (or, equivalently, pure).

Here's an example using the IO monad. You can adapt it to your case.

main :: IO ()
main = do
   putStrLn "enter True of False"
   x <- readLn   
   res <- if x
      then do
         putStrLn "You entered True: write somehting now"
         getLine             -- its result is bound to res
      else return "nothing"  -- res is bound to "nothing"
   putStrLn $ "final result: " ++ res

Main points:

  • If a branch of your if contains an action that you want to run right now, you must avoid let res = if ... and instead use res <- if .... The former defines res to be the action itself, the latter executes the action and defines res to be the result produced by the action.
  • Both braches of res <- if ... must have the same monadic type. Use return to make it so.

There is no way to use the standard if with a monadic then branch and a non-monadic else branch. At best, we can define a custom "if" function that does that, e.g.

ifMon :: Monad m => Bool -> m a -> a -> m a
ifMon True x  _ = x
ifMon False _ y = return y

This can be used as

do
   putStrLn "blah"
   res <- ifMon condition
          (do
              putStrLn "then branch"
              return "abc")
          "else result, non monadic"

You can have a similar helper function for the case where the then branch is "pure" and the else branch is monadic.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
chi
  • 111,837
  • 3
  • 133
  • 218
  • Thank you, and there is another question: - x = if condition x then do_some x else x is widely-spread case. It there some "special syntax" for this. Both in "pure-functions" and "do-notation" ? – Konstantin Kazartsev Jan 12 '22 at 22:47
  • @KonstantinKazartsev I'm sorry but I don't understand your question. How does that differ from the case I handled in my answer above? – chi Jan 13 '22 at 01:06
  • In your answer above you describe a **general** rule (consistent types in both branches of if - thant's really reasonable rule). And now I ask: "You are right, but is there any special rules for often use-case?" – Konstantin Kazartsev Jan 13 '22 at 01:19
  • @KonstantinKazartsev I edited adding a comment on that. – chi Jan 13 '22 at 09:02