5
import           Control.Monad.ST
import           Data.STRef

import qualified Data.Vector.Unboxed         as V
import qualified Data.Vector.Unboxed.Mutable as MV

-- what the heck is that s?
new0 :: (MV.Unbox a, Num a) => ST s (MV.STVector s a)
new0 = do
  v <- MV.new 10
  MV.write v 1 10
  MV.write v 2 10
  return v

new1 :: (MV.Unbox a) => IO (MV.IOVector a)
new1 = MV.new 10

new2 :: (MV.Unbox a, Num a) => IO (MV.STVector RealWorld a)
new2 = do
  v <- MV.new 10
  MV.write v 1 10
  return v

I'm implementing the QuickSort algorithm and I choose Data.Vector.Unboxed.Mutable

I'm completely lost regarding two issues:

  • How to choose the right Monad: IO or ST
  • How to get the value out of ST. Could anyone show me how to print new0 ?

Solutions:

I got some inspirations from this example code

For a good understanding of ST monad, check the original paper: Lazy Functional State Threads

Here's a code snippet showing how to use mutable vectors for quicksort:

import           Control.Monad.Primitive
import           Control.Monad.ST            (runST)
import           Prelude                     hiding (read)

import           Data.List                   (partition)
import qualified Data.Vector.Unboxed         as V
import qualified Data.Vector.Unboxed.Mutable as M

partitionV :: (PrimMonad m, Ord a, V.Unbox a) 
           => M.MVector (PrimState m) a -> Int -> Int -> m Int
partitionV v p r = do
  let i = p
  x <- M.unsafeRead v r
  go i p x
  where
    go i j x | j < r = do
               jv <- M.unsafeRead v j
               if jv < x
                 then M.unsafeSwap v i j >> go (i+1) (j+1) x
                 else go i (j+1) x
    go i _ _ = do
      M.unsafeSwap v i r
      return i

quickSortV :: (PrimMonad m, Ord a, V.Unbox a) 
           => M.MVector (PrimState m) a -> m ()
quickSortV v | M.length v < 2 = return ()
quickSortV v = do
  i <- partitionV v 0 (M.length v - 1)
  quickSortV (M.unsafeSlice 0 i v)
  quickSortV (M.unsafeSlice (i + 1) (M.length v - 1 - i) v)
    -- note the difference between for loop and recursive call

test l = runST $ do
  mv <- V.thaw l
  quickSortV mv
  V.freeze mv

quickSort :: (Ord a) => [a] -> [a]
quickSort []       = []
quickSort (x : xs) =
    let (lt, gt) = partition (<= x) xs
    in  quickSort lt ++ [x] ++ quickSort gt
Will Ness
  • 70,110
  • 9
  • 98
  • 181
daydaynatation
  • 550
  • 2
  • 8
  • 1
    You can work with `runST`: https://hackage.haskell.org/package/base-4.15.0.0/docs/Control-Monad-ST.html#v:runST – Willem Van Onsem Apr 01 '21 at 11:01
  • 2
    @WillemVanOnsem `runST new0` gave me crazy type errors – daydaynatation Apr 01 '21 at 11:02
  • `new` it self is a `ST` action - I don't think there is a reasonable way to print it - to get the value out of `ST` you can use [runST](https://hackage.haskell.org/package/base-4.15.0.0/docs/Control-Monad-ST.html#v:runST) - but here this will not work (`ST` is build in a clever way - you cannot get out the `s`) - I cannot check it right now but you should be able to use [freeze](https://hackage.haskell.org/package/vector-0.12.2.0/docs/Data-Vector.html#v:freeze) at the end (`return $ freeze v`) and return a `Vector` instead (signature will change) – Random Dev Apr 01 '21 at 11:06
  • concerning the "right Monad" - I'm afraid that depends on your use case - `ST` is a way to do *mutable* stuff in a *pure'ish* context (meaning not in `IO`) - so if you don't want to have `IO` everywhere around your functions use that - if you are in IO anyway use it – Random Dev Apr 01 '21 at 11:09
  • 3
    The whole point of the `s` in `ST s SomeType` is to make it possible for `runST` to verify that `SomeType` does not return any references (which are tainted by `s`). Hence, if you have a (polymorphic) `ST s Int` value, you can use `runST` on that, but if you use `runST new0` you must be stopped by the type system since you are trying to make references (like `MV.STVector s a`) outlive `runST`, creating dangling pointers. – chi Apr 01 '21 at 11:09
  • ([BTW](https://stackoverflow.com/a/7719971/849891)). – Will Ness Apr 01 '21 at 23:03

1 Answers1

8

The ST monad is for when you want to take some immutable data, make it temporarily mutable, do something with it, and make it immutable again. In particular, there is no way to return a mutable result from the ST monad (by design).

In particular, STVector is mutable, so it can never leave the ST monad. So there is no way to "print out" new0. You would have to convert it to something immutable to return it out of the ST monad.

If you're wanting to mutate something, print it out, mutate it a bit more, print it out, etc., you probably want the IO monad. The ST monad is for making something temporarily mutable, to eventually produce an ordinary immeduate result.

Regarding the s type variable: It's a slight hack that enforces the property that mutable data cannot leave ST. The runST function requires an action that works for every possible choice of s. This means it's impossible to return anything that contains s. Since all the mutable stuff has s in its type signature, this enforces our guarantee.

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • 2
    This is helpful. They should put it in the doc, in layman's term and with an example – daydaynatation Apr 01 '21 at 12:58
  • Question regarding `ST`: Wouldn't it break purity of the code? Immutable in, immutable out, I get it. But in between things mutates... – daydaynatation Apr 01 '21 at 20:56
  • @daydaynatation all that matters is that your function is pure on the outside, i.e. as observed from the outside, in its interactions, IOW always producing the same results from the same inputs. then it doesn't matter how it does its job "on the inside". say a Bessel function that calls a C library to do the actual job. or a *quicksort* function that needs to work on a mutable array to actually do the partition as prescribed by the algorithm. the mutations that are unobservable from the pure world are OK. – Will Ness Apr 01 '21 at 22:37
  • @daydaynatation An important first step in answering that question: can you say what "purity" means to you, exactly? (If not, then the answer has to start by saying what Haskellers traditionally mean when *they* say it!) – Daniel Wagner Apr 02 '21 at 05:09
  • @DanielWagner, by pure I mean same input always produces same output – McBear Holden Apr 02 '21 at 05:40
  • @WillNess How is my function pure on the outside? Will it always give the same output for the same input? – daydaynatation Apr 02 '21 at 06:29
  • 1
    @daydaynation Yes, it will always give the same output for the same input. `ST` is designed to let you use mutation internally to implement a pure function, but to limit the mutation you can do so that you cannot possibly use it to make the function you're implementing (that uses `ST` internally) return different results for the same input. (That's exactly what the funky `s` type variable is used for) – Ben Apr 02 '21 at 07:47
  • 2
    @daydaynatation you are writing a quicksort function. is sorts its input, and produces its sorted version. even if you've implemented it incorrectly, it will always do *something*, *some thing*, the *same* thing according to the code you wrote. the only way to introduce unpredictable change is to use some I/O means by an `IO` code, like random, or making some decision depending on some unpredictable external source. but there's no escape from `IO`. there is escape from `ST` because there's no unpredictability in it, no randomness, no I/O. all *that* lives in `IO` monad, not `ST` monad. – Will Ness Apr 02 '21 at 10:27
  • @WillNess I think I will fall in love with `ST` for allowing some kind of escape while still keep it safe – daydaynatation Apr 02 '21 at 11:14
  • 1
    Do watch out for `unsafeInterleaveST` though. There are times when it is useful, which is why it exists. But it easily can violate purity if misused. – Carl Apr 02 '21 at 15:37