Lifting a value in the State monad in Haskell

大城市里の小女人 提交于 2019-12-10 20:33:41

问题


I am writing a Sudoku generator/solver in Haskell as a learning exercise.

My solve function takes in a UArray but returns a State Int (UArray ...) so that it can also return the maximum difficulty level that it found while solving.

This is my function so far (still in the very experimental early stage):

import Control.Monad.State       (State, put)
import Control.Monad.Trans.Class (lift)
import Data.Array.MArray         (thaw)
import Data.Array.ST             (runSTUArray)
import Data.Array.Unboxed        (UArray)

-- ...

type Cell = Word16

solve :: UArray (Int, Int) Cell -> State Int (UArray (Int, Int) Cell)
solve grid = do
  return $ runSTUArray $ do
    arr <- thaw grid
    lift $ put 42
    return arr

It does not really do anything with the mutable array yet. I am simply trying to get it to type check with the put 42, but currently get the following error:

  • Couldn't match kind ‘*’ with ‘* -> *’
    When matching the kind of ‘ST’
  • In a stmt of a 'do' block: lift $ put 42
    In the second argument of ‘($)’, namely
      ‘do arr <- thaw grid
          lift $ put 42
          return arr’
    In the second argument of ‘($)’, namely
      ‘runSTUArray
         $ do arr <- thaw grid
              lift $ put 42
              return arr’
     |
 128 |     lift $ put 42
     |     ^^^^^^^^^^^^^

回答1:


runSTUArray ... is a pure value, it does not know anything about "outer monad". And State cares about how you use it, you cannot pass it opaquely into ST.

What you could do:

Option1: change the whole program to move more logic to ST side. Instead of State you'd use STRef then:

solve :: ST s (STRef Int) -> ST s (UArray (Int, Int) Cell) -> ST s ()
...

Option2: manually extract it and pass it to ST, then get back and put explicitly. But there is complication. runSTUArray does not allow getting another value together with the array. I don't know how it can be done safely with current array functions. Unsafely you could re-implement better runSTUArray which can pass another value. You could also add fake cells and encode the new state there.

The way to export another value exists in the vector package, there is (in new versions) createT function which can take not bare vector but a structure containing it (or even several vectors). So, overall, your example would be like:

import Control.Monad.State (State, put, get)
import Data.Word (Word16)

import qualified Data.Vector.Unboxed as DVU

type Cell = Word16

solve :: DVU.Vector Cell -> State Int (DVU.Vector Cell)
solve grid = do
  oldState <- get
  let (newState, newGrid) = DVU.createT (do
          arr <- DVU.thaw grid
          pure (oldState + 42, arr))
  put newState
  pure newGrid

vectors are one-dimensional only, unfortunately




回答2:


solve grid has form return $ .... This means that State Int (UArray (Int, Int) Cell) is just specialized Monad m => m (UArray (Int, Int) Cell) - the ... does not have access to the features of this specific monad, it's just a UArray (Int, Int) Cell value that you return.




回答3:


I was able to get a slight variation to compile and run after changing the State monad to a tuple (Int, Grid):

import Control.Monad.ST    (ST, runST)
import Data.Array.MArray   (freeze, thaw, writeArray)
import Data.Array.ST       (STUArray)
import Data.Array.Unboxed  (UArray)
import Data.Word (Word16)

type Cell = Word16
type Grid = UArray (Int, Int) Cell

solve :: Grid -> (Int, Grid)
solve grid =
  runST $ do
    mut <- thaw grid :: ST s (STUArray s (Int, Int) Cell)
    writeArray mut (0, 0) 0 -- test that I can actually write
    frozen <- freeze mut
    return (42, frozen)

This works fine for my application.



来源:https://stackoverflow.com/questions/49350572/lifting-a-value-in-the-state-monad-in-haskell

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!