zip / make a Map from mfix'd values

I had a problem where I wanted to construct a Map from a set of values, and I wanted this map to *also* be accessible (in "Tying the knot"https://gitlab.haskell.org/ghc/ghc/-/wikis/commentary/compiler/tying-the-knot sort of way) to the datatype that's actually being run through.

The code in question:

let tm' = TypeMap
    { tmTVarMap  = Map.fromList $ zip sTVs mTVs
    , tmUnionMap = Map.fromList $ zip sUnions mUnions
    , tmAssocMap = Map.fromList $ zip sAssocs mAssocs
    }

(mUnions, mAssocs) <- withTypeMap tm' $ do
	mmUnions <- traverse mUnion muUnions
	mmAssocs <- traverse selectInstance' muAssocs
	pure (mmUnions, mmAssocs)

a simplified model

To make testing easier, I've simplified the model to:

let idxs = [1..10]
let mfn = pure :: a -> IO a  -- whatever function
let map = zip idxs nums

print $ length map  -- use before define

nums <- traverse mfn idxs
print $ length map

Notice, that both keys and values are of the *same length*.

This code fails with "fixiomap: cyclic evaluation in fixIO".

perils of fixIO

Unfortunately, even though the length of the list "is known", I cannot use this Map without it throwing the cyclic evaluation error.

The reason is, that I cannot even "interact" with the m [a] value. I can only place it somewhere, but I may not rely on its shape. Evaluating anything from it will cause the error.

So, can you even construct a Map from this? The answer is *yes*.

my shitty first solution

If I can't interact with the list part directly, I'll just put the whole list inside as a value, and select it there:

wtfzip :: [a] -> [b] -> [(a, b)]
wtfzip xs ys = zip xs [ ys !! i | i <- [0..] ]

The whole ys is being placed inside, and only later do we index it (as in, when we want to evaluate that value thunk).

It works!

Caveats: the lists need to be the same length (or the first one must be shorter). Otherwise error in (!!) .

Note, that I can't just do length ys , because then we're back to square one.

a non retarded solution (not mine)

zip' [] _ = []
zip' (x:xs) ~(y:ys) = (x, y) : zip' xs ys

After I showcased my solution on Haskell Discord, a guy by the name of *L0neGamer* came up with a much better one. See that lazy pattern matchhttps://wiki.haskell.org/Lazy_pattern_match ?

Caveats: same as last time, actually. We cannot create a case for the second list to be empty, because then it would always match (with that lazy pattern). It also crashes if the second list is longer, same as last time.

Why does this work? Adding laziness does not evaluate the whole ~(y:ys) pattern.

But how can we recursively call zip' if we haven't yet deconstructed this value? Laziness allows us to bind those variables without actually doing the deconstruction. Another guy called *mniip* deconstructed this case for me (ayyyyy!!!!!!!):

let y  = case rhs of { y:_ -> y }
    ys = case rhs of { _:ys -> ys }

Two thunks are created based on the rhs value. Since the second pattern is lazy, we can do the same thing with ys again in zip' .

I hope I can internalize dis shid.

Ya can also imagine zip' as matching on Maybe [b] :

zip' [] _ = []
zip' (x:xs) ~(Just (y:ys)) = (x, y) : zip' xs ys

The list it produces will have length of xs , but as soon as I try to access an element, the program crashes.