Fork me on GitHub

Hello Peeps!


I am having a total macro fail. I have a ns, x, in which I've got a macro defined

(defmacro gauge
  ([metric value]
   `(statsd/gauge ~(format-metric metric ".value") ~value (context))))
format-metric is a defn. If I call the macro as-is with a string value for metric (e.g. (x/gauge "foo" 1)) it works fine. If I call it with a function that will return a string, e.g. (x/gauge (name :foo) 1) I get an exception because (name :foo) is not a string. This is all fine, I understand why it's happening. I can (macroexpand (x/gauge ~(name :foo) 1)) and get what I want, but that requires knowledge it is a macro and of macro escape chars in the caller. I can't for the life of me work out what sequence of macro escape characters I need in the defmacro` to allow metric to work as either a bare string or a function that will resolve to a string without the caller having to do anything special


@carr0t does (statsd/gauge (format-metric metric ".value") value (context))))` not do what you want ?


facepalm You know, I think it might. ☝️:skin-tone-2: is what was there already, with the ~ on the entire format-metric call, and it didn't occur to me I'd need to get rid of that for a ~ on metric (which I had already tried) to work


yw - and if you aren't already using it, CIDER's macroexpand is my favourite tool for helping me to bludgeon macros into submission - point it at any piece of code and macroexpand away


at the risk of being a scumbag, does this require a macro? i can't visually parse what's going on here but it feels like a normal HOF would work &/or failing that a partial application


format-metric inserts the namespace, so that we always know where it was emitted from. That doesn't work (without manually providing it) unless it's a macro


namespace of the calling location or definition location?


(i assume the former)


The former


i'd never have thought to write a macro for that, if i wanted to know more info i think i'd have parameterised it, but then i think i'm relatively macro-averse for a lisper heh


i guess it's the implicitness that i don't like when it comes to a lot of metaprogramming (thinking of ruby perhaps more than clojure actually for some of the worst offending code i've seen)

Jordan Robinson13:03:33

hot take time: I think it's good to be macro-averse


the first rule of macroclub is real - but i have written fns which take explicit "location" params, alongside a macro which expands to that fn with the "location" param filled in


Agreed. They're often more trouble than they're worth. But in this case I think it's useful


We've got various calls to the same function we want metrics on all through the codebase, and we want to be able to tell where that call originated. Rather than wrapping every one in an independent metric with params etc, we use this around the function itself, in another macro. Then all our calls are to that macro, and they all get unique metrics recorded based on their namespace


me, coming to after too long working with monadic pipelines


> "Come back, so we can be young men write macros together again"


macros go great with your monadic pipelines @alex.lynham, you've just been working in a language which doesn't have macros, so you've gotten all stockholmed to doing without 😬


i think that a lot of the problems that we've had in prod have been at runtime so i guess i'm now starting to think i want compile time to be even more aggressive is what it is


also yeah, a period of strong static typing has had me thinking a bit differently about issues i've had in the past where maybe the root cause was dynamic typing (if you get all the way down to it :thinking_face:)


i would dearly like some static typing sauce on my dynlangs


I like gradual typing a lot. But adding it to the language not designed to be statically typed can be tricky (Typed Clojure).


maybe in my heart of hearts i want carp with a good standard library, idk


What do you use monadic pipelines for?


We use it for our controllers & middlewares/interceptors, where they are basically AppState -> Either AppState shaped and it gives us stateless controllers and middlewares while retaining nice “Developer Experience”.


we're in typescript, writing serverless/aws lambdas


i've looked at carp a few times and lux more than a few times 🙂


so all our handers init a system state, then just call a fold action that resolves a big 'ol chain of deferred Either monads down to either a success or error HTTP response which we return


Monadic pipelines in TS?


totally agree @jiriknesl - if static checking isn't there from the start then it's probably going to be quite api-breaking


then every single entity basically just plugs together the database lookups and all the transformations required to get from HTTP request -> HTTP response and it's all on rails


we're using fairly basic monadic pipelines in clj in our api and routing


> required to get from HTTP request -> HTTP response and it’s all on rails This is exactly what we want to achieve. Stateless applifecycle that will give convenient methods you could have in Rails/Django, but without any hidden state.


so our handlers look a bit like

export const usersPublicGet = async (event: requests.MinimalRequestEvent): Promise<responses.Response> => {
  const system: db.System = await db.getSystem();
  const client = system.pgclient;

  try {

    const response = await p.pipeline(event, up.getPublicPipeline)(system)();
    return response;

  } catch (err) {
    return responses.respond500('http/error', 'Something went wrong');
  } finally {
    await db.closeDbConn(client);


where the const response line is where the magic happens


and then a pipeline is just lots of ReaderTaskEithers where the System is in the Reader and the TaskEither is just a deferred/async Either

const getPublicPipeline = (event: requests.MinimalRequestEvent): RTE.ReaderTaskEither<db.System, t.ErrorType, t.EntityType> =>
    .bind('userId', RTE.fromEither(requests.getIdFromPath(event)))
    .bindL('getUserRes', ({ userId }) => uDb.getUserRTE(userId))
    .bindL('user', ({ getUserRes }) => RTE.fromEither(uXfms.getUserFromResE(getUserRes)))
    .bindL('userNoPII', ({ user }) => RTE.fromEither(uXfms.userToShareableE(user)))
    .bindL('getUserFileRes', ({ user }) => fDb.getFileRTE(user.file_uuid))
    .bindL('userFile', ({ getUserFileRes }) => RTE.right(fXfms.getUserFileDataFromRes(getUserFileRes)))
    .bindL('userNoPIIWithFile', ({ userNoPII, userFile }) => RTE.right(R.mergeRight(userNoPII, { file: userFile })))
    .return(({ userNoPIIWithFile }) => { return { entityType: t.EntityTypes.user, entity: userNoPIIWithFile }; });


i do like the destructuring approach to binding variables - it's a neat workaround


it's less terse but very explicit about what's going on


the gotcha is that if you don't deref the var in a bind (i.e. you use a bind not a bindL in a step that's later), you sometimes see async things go awry, but i guess i expected that cos of how mlet works, so i only realised it could cause a bug recently


i think that's why most people just use pipe and chain from the fp-ts core lib


orly - i was getting the impression that bindL was semantically bind, but supplying a map of all the in-scope bound variables to the function, rather than just the last bound variable ?


iirc there's a gotcha


oh actually i think it might be to do with the ordering of do vs doL


i think do can execute earlier than it's listed in a chain if it doesn't ref the context


ohhhh, is it actually an applicative-do, rather than a monadic-do ?


well it seems to be monadic when the context is in scope, so possibly it's just a bug


it was just one time where something v weird happened so i needed to add a binding


although maybe i'm misremembering




it would probably look the same... applicative-do looks, and often behaves, like a regular monadic do ( but steps can happen concurrently


hmmm, there's a specific sequence form (i think that's the api i'm thinking of) to do that style of parallel/applicative stuff, so maybe i'm just remembering wrong