3

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
     |     ^^^^^^^^^^^^^
Ralph
  • 31,584
  • 38
  • 145
  • 282
  • I think I see my own problem, but have no idea how to solve it. The `ST` monad is not embedded in a `StateT` transformer, so the `put` operation cannot be lifted. – Ralph Mar 18 '18 at 17:20

3 Answers3

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

max630
  • 8,762
  • 3
  • 30
  • 55
  • FYI https://stackoverflow.com/a/49356530/2303202 looks possible with arrays – max630 Mar 19 '18 at 06:42
  • No, signature `((forall s . ST s a) -> b)` does not match the case of `runSTUArray`, because `a` has `s` in its parameters. So the linked trick would not help - if you try to pass mutable array to another ST extractor it would not compile because of "type variable ‘s’ would escape its scope" – max630 Mar 19 '18 at 22:09
0

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.

Gurkenglas
  • 2,317
  • 9
  • 17
  • If I move the `put 42` to the line above the `return $ runSTUArray $ do`, it compiles and runs properly. I'm just trying to figure out how set the state in the outer `State` monad from inside the `ST` monad. – Ralph Mar 18 '18 at 17:08
0

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.

Ralph
  • 31,584
  • 38
  • 145
  • 282