Ремонт принтеров, сканнеров, факсов и остальной офисной техники


назад Оглавление вперед




[21]

ds <- gets desc

return $ (elem a as) && (elem d ds)

-add a Queen to the board in all allowed positions addQueens :: NDS ()

addQueens = do rs <- gets ranks fs <- gets files

allowed <- filterM inDiags [Pos f r f <- fs, r <- rs] tell [show (length allowed) ++ " possible choices"] msum (map addQueen allowed)

-Start with an empty chess board and add the requested number of queens, -- then get the board and print the solution along with the log

main :: IO ()

main = do args <- getArgs

let n = read (args!!0)

cmds = replicate n addQueens

sol = ("getSolution" initialState) $ do sequence cmds

gets board

case sol of

Just (b,l) -> do putStr $ show b -- show the solution

putStr $ unlines l -- show the log Nothing -> putStrLn "No solution"

The program operates in a similar manner to the previous example, which solved the kalotan puzzle. In this example, however, we do not test for consistency using the guard function. Instead, we only create branches that correspond to allowed queen positions. We use the added logging facility to log the number of possible choices at each step and the position in which the queen was placed.

3.7.3. Heavy lifting

There is one subtle problem remaining with our use of multiple monad transformers. Did you notice that all of the computations in the previous example are done in the combined monad, even if they only used features of one monad? The code for these functions in tied unneccessarily to the definition of the combined monad, which decreases their reusability.

This is where the lift function from the MonadTrans class comes into its own. The lift function gives us the ability to write our code in a clear, modular, reusable manner and then lift the computations into the combined monad as needed.

Instead of writing brittle code like:

logString :: String -> StateT MyState (WriterT [String] []) Int logString s = ...

we can write clearer, more flexible code like:

logString :: (MonadWriter [String] m) => String -> m Int

ФП 02005-03 01

Лист 69

№ докум.

Копиоова Фоомат


£ сс

logString s = ...

and then lift the logString computation into the combined monad when we use it.

You may need to use the compiler flags -fglasgow-exts with GHC or the equivalent flags with your Haskell compiler to use this technique. The issue is that m in the constraint above is a type constructor, not a type, and this is not supported in standard Haskell 98.

When using lifting with complex transformer stacks, you may find yourself composing multiple lifts, like lift. lift. lift $ f x. This can become hard to follow, and if the transformer stack changes (perhaps you add ErrorT into the mix) the lifting may need to be changed all over the code. A good practice to prevent this is to declare helper functions with informative names to do the lifting:

liftListToState = lift . lift . lift

Then, the code is more informative and if the transformer stack changes, the impact on the lifting code is confined to a small number of these helper functions.

The hardest part about lifting is understanding the semantics of lifting computations, since this depends on the details of the inner monad and the transformers in the stack. As a final task, try to understand the different roles that lifting plays in the following example code. Can you predict what the output of the program will be?

Code available in example26.hs

- this is our combined monad type for this problem type NDS a = StateT Int (WriterT [String] []) a

{- Here is a computation on lists -}

-- return the digits of a number as a list getDigits :: Int -> [Int] getDigits n = let s = (show n)

in map digitToInt s

{- Here are some computations in MonadWriter -}

-- write a value to a log and return that value logVal :: (MonadWriter [String] m) => Int -> m Int logVal n = do tell ["logVal: " ++ (show n)] return n

-- do a logging computation and return the length of the log it wrote getLogLength :: (MonadWriter [[a]] m) => m b -> m Int getLogLength c = do ( ,l) <- listen $ c

return (length (concat l))

- log a string value and return 0

ФП 02005-03 01

Лист 70


logString :: (MonadWriter [String] m) => String -> m Int logString s = do tell ["logString: " ++ s] return 0

{- Here is a computation that requires a WriterT [String] [] -}

-"Fork" the computation and log each list item in a different branch. logEach :: (Show a) => [a] -> WriterT [String] [] a

logEach xs = do x <- lift xs

tell ["logEach: " ++ (show x)] return x

{- Here is a computation in MonadState -}

-increment the state by a specified value addVal :: (MonadState Int m) => Int -> m () addVal n = do x <- get

put (x+n)

{- Here are some computations in the combined monad -}

-- set the state to a given value, and log that value setVal :: Int -> NDS () setVal n = do x <- lift $ logVal n put x

-- "Fork" the computation, adding a different digit to the state in each branch.

-Because setVal is used, the new values are logged as well. addDigits :: Int -> NDS ()

addDigits n = do x <- get

y <- lift . lift $ getDigits n setVal (x+y)

{- an equivalent construction is: addDigits :: Int -> NDS () addDigits n = do x <- get

msum (map (\i->setVal (x+i)) (getDigits n))

{- This is an example of a helper function that can be used to put all of the lifting logic

in one location and provide more informative names. This has the advantage that if the

transformer stack changes in the future (say, to add ErrorT) the changes to the existing

lifting logic are confined to a small number of functions.

ФП 02005-03 01

Лист 71

№ докум.

Копиоова Фоомат



[стр.Начало] [стр.1] [стр.2] [стр.3] [стр.4] [стр.5] [стр.6] [стр.7] [стр.8] [стр.9] [стр.10] [стр.11] [стр.12] [стр.13] [стр.14] [стр.15] [стр.16] [стр.17] [стр.18] [стр.19] [стр.20] [стр.21] [стр.22] [стр.23] [стр.24] [стр.25]