Sneaking polysemy in - Polysemy

Welcome to the Functional Programming Zulip Chat Archive. You can join the chat here.

Sridhar Ratnakumar

So in my neuron project I have a pure function that looks at an ADT, does bunch of checks and returns something. But one of the constructors will have to use nextRandom :: IO UUID to create a random string, and that's an IO action. Everything else is pure code.

It would suck to make this entire function an IO action just for that one constructor.

I'm wondering if it would be unsuitable to sneak in polysemy just for this function. And then create a separate effect for the test (where I pass a predetermined "random" hash). Makes every thing else pure, and would give me an excuse to use and learn the newer effects system, but in a smaller part of the code.

Georgi Lyubenov // googleson78

how "deep in the stack" is this function, i.e. from main?

Sridhar Ratnakumar

Very close to main. In CLI argument handlers. But the function is small and you can tell what it does from its argument (without knowing the rest of the code base)

Sridhar Ratnakumar

Well, here it is in its entirety (minus the IO action):

-- | Create a new zettel ID based on the given scheme without conflicting with
-- the IDs of existing zettels.
nextAvailableZettelID
  -- | Existing zettels
  :: Set ZettelID
  -> IDScheme
  -> Either Text ZettelID
nextAvailableZettelID zs = \case
  IDSchemeDate day -> do
    let dayIndices = nonEmpty $ sort $ flip mapMaybe (Set.toList zs) $ \case
          ZettelDateID d x
            | d == day -> Just x
          _ -> Nothing
    case last <$> dayIndices of
      Nothing -> pure $ ZettelDateID day 1
      Just 99 -> throwError "Ran out of date ID indices"
      Just idx -> pure $ ZettelDateID day (idx + 1)
  IDSchemeHash -> do
    -- TODO: IO!!!!!!!!!!!!!!!
    -- let h = T.take 8 $ UUID.toText
    undefined
  IDSchemeCustom s -> runExcept $ do
    zid <-
      fmap ZettelCustomID
        $ liftEither
        $ first ("Bad custom ID: " <>)
        $ parse customIDParser "<next-id>" s
    if zid `Set.member` zs
      then throwError "An zettel with that ID already exists"
      else pure zid
Sridhar Ratnakumar

IDSchemeHash is the constructor that will require IO.

Georgi Lyubenov // googleson78

I would do manual UUID passing in this case, if you don't actively plan on having more effects

idk, seems like overkill for a single thing

even with some kind of effect system, I think it would still be better to have this function be effect-less, because all your other cases (the majority of your cases) would get "polluted" with more pures

Sridhar Ratnakumar

Well, it is already in an error monad (Either or ExceptT)

Georgi Lyubenov // googleson78

and a Reader/Input (over Set ZettelID) I guess, if we're going to go looking for effects to insert

Sridhar Ratnakumar

(Member (Error ZettelIDConflict) r, Member UUID r) => m ZettelID kind of looks niiiice

Georgi Lyubenov // googleson78
nextZettel ::
  Members [Error ZettelIDConflict, Input (Set ZettelID), UUID] r =>
  Sem r ZettelID
Sridhar Ratnakumar

Or Polysemy.Random. I just want to produce hashes like cc982318

Sridhar Ratnakumar
nextZettel ::
  Members '[Error ZettelIDConflict, Input (Set ZettelID), UUID] r =>
  IDScheme ->
  Sem r ZettelID
Sridhar Ratnakumar

What's the benefit of using Input as opposed to passing it as a function argument?

Georgi Lyubenov // googleson78

If you pass around the Set a lot it's worth it

Georgi Lyubenov // googleson78

same as the Either/Except - if you have a lot of stuff that could fail it's worth it

Sridhar Ratnakumar

Oh right. The familiar config threading annoyance.

Sridhar Ratnakumar

(In this case, though, it won't be passed further)

Sridhar Ratnakumar

From the changelog,

Moved Random effect to polysemy-zoo

Ah

Sridhar Ratnakumar

polysemy-zoo has unwieldy deps

Sridhar Ratnakumar

Painful deps, I'm going with GADTs

data IDScheme a where
  IDSchemeDate :: Day -> IDScheme ()
  IDSchemeHash :: IDScheme UUID
  IDSchemeCustom :: Text -> IDScheme ()
Sridhar Ratnakumar

FWIW, here it is https://github.com/srid/neuron/pull/152/files#diff-73c560a0a454d2da4e37d64aaabb9b92

Also adds --id=<custom> and --id-date. Date ID is still the default . And neuron new adds the creation date to the zettel metadata regardless of the ID scheme used. For #151
Sridhar Ratnakumar

Separates out IO stuff in genVal, leaving the rest of mechanism in the pure function.

Sridhar Ratnakumar

^^ dependent type @TheMatten ?