👋
måning
Hello Peeps!
moin moin
morning
morning
Aloha
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 specialMorn'
@carr0t does (statsd/gauge (format-metric
metric ".value") value (context))))` not do what you want ?
🤦 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
Thanks!
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
interesting
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)
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).
mmm agreed
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
i'm working on our nextgen stuff atm - https://github.com/mccraigmccraig/promisefx
> 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) {
console.log(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 ReaderTaskEither
s 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> =>
Do(RTE.readerTaskEither)
.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 }; });
https://gist.github.com/the-frey/ad36baa1e86c4669e9ab45f43326e5a0 <--- ^ this example w/syntax highlighting
i do like the destructuring approach to binding variables - it's a neat workaround
ikr
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
:woman-shrugging:
it would probably look the same... applicative-do
looks, and often behaves, like a regular monadic do
(https://gitlab.haskell.org/ghc/ghc/-/wikis/applicative-do) but steps can happen concurrently
alet
in funcool/cats
does the same thing - https://github.com/funcool/cats/blob/master/src/cats/core.cljc#L413
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