I've been dissecting this one-liner solution for aoc day 14 and came across an elegant impure recursive solution:
def s(x,y):
if y > h: return True
if (x, y) in m: return False
return next((r for d in (0,-1,1) if (r:=s(x+d,y+1))), None) or m.add((x,y))
One way you could make this pure is by explicitly passing and returning the set m from the function s (i.e. s :: int -> int -> set -> (bool, set)).
However, I've also read about how the reader/writer/state monads save you from having to pass the extra parameter and handle the tuple result an am interested in porting this recursion to haskell.
I found a haskell solution on the reddit that looks like it may do the same recursion (as well as two more that don't).
fill :: (MArray a Bool (ST s), Ix i, Num i, Show i) => a (i, i) Bool -> i -> ST s (Int, Int)
fill blocks maxY = do
counterAtMaxY <- newSTRef Nothing
counter <- newSTRef 0
let fill' (x, y) = readArray blocks (x, y) >>= flip bool (pure ()) do
when (y == maxY) $ readSTRef counterAtMaxY >>= maybe
(readSTRef counter >>= writeSTRef counterAtMaxY . Just) (const $ pure ())
when (y <= maxY) $ fill' (x, y + 1) >> fill' (x - 1, y + 1) >> fill' (x + 1, y + 1)
writeArray blocks (x, y) True >> modifySTRef' counter (+ 1)
fill' (500, 0)
counterAtMaxY <- readSTRef counterAtMaxY
counter <- readSTRef counter
pure (fromMaybe counter counterAtMaxY, counter)
Could someone confirm that this indeed is a port of the python solution. If so could they baby me through following how the recursion is happening?
I still am not Haskell literate. I can kind of make out that fill' (500, 0) means m >>= \_ -> fill' (500, 0), which means discard the current state, and create a new monad independently (something gets preserved but I'm confused what)??. I also don't understand monad transformers at all.
The Haskell solution does part 2 of the question simultaneously, so maybe someone can factor that out so there's no confusion between the cartesian coordinates and the pair of ints containing the solution.
Below is a fairly close translation of your Python code to Haskell. Some remarks on the differences:
hbecomes a local parameter, andm :: Set (Int, Int)gets passed implicitly in theStatemonad, accessed usinggetandmodify.return/puredoesn't abort the rest of the function, you have to put it at the end of the block). On the other hand,ifexpressions must have anelseclause, so that forces you to do the right thing anyway.True.addfunction in Python returnsNone, which gets interpreted asFalsein conditionals. In Haskell we don't like this kind of overloading; instead, we explicitly attach theFalsevalue to the value-less actionadd (x, y),add (x, y) *> pure False.execStateto "run the monadic program"s h0 500 0with an initial statem0, obtaining its final state. That "program"s h0 500 0 :: M Boolis actually a pure functionSet (Int, Int) -> (Bool, Set (Int, Int)), and allexecStatedoes is to apply that to the initial state and project out the second component of the output pair. The point of the "state monad" is that such a function can be defined with the syntax of an imperative language ("do-notation").