clojure-uk

A place for people in the UK, near the UK, visiting the UK, planning to visit the UK or just vaguely interested to randomly chat about things (often vi and emacs, occasionally clojure). More general the #ldnclj
djm 2021-03-09T06:57:44.215500Z

👋

mccraigmccraig 2021-03-09T07:09:24.215900Z

måning

dharrigan 2021-03-09T07:14:21.216100Z

Hello Peeps!

thomas 2021-03-09T09:18:05.216300Z

moin moin

jasonbell 2021-03-09T09:42:57.216500Z

morning

alexlynham 2021-03-09T10:26:38.216600Z

morning

danm 2021-03-09T10:45:47.216800Z

Aloha

danm 2021-03-09T13:33:01.223Z

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

2021-03-09T13:37:29.223600Z

Morn'

mccraigmccraig 2021-03-09T13:42:04.224900Z

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

danm 2021-03-09T13:44:19.226200Z

🤦 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

danm 2021-03-09T13:44:52.226700Z

Thanks!

mccraigmccraig 2021-03-09T13:46:55.228Z

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

alexlynham 2021-03-09T13:48:11.228100Z

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

danm 2021-03-09T13:49:40.229400Z

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

alexlynham 2021-03-09T13:51:51.229600Z

namespace of the calling location or definition location?

alexlynham 2021-03-09T13:52:01.229700Z

(i assume the former)

danm 2021-03-09T13:53:52.229900Z

The former

alexlynham 2021-03-09T13:55:17.230Z

interesting

alexlynham 2021-03-09T13:56:13.230100Z

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

alexlynham 2021-03-09T13:56:27.230200Z

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)

2021-03-09T13:58:33.231700Z

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

mccraigmccraig 2021-03-09T13:59:09.232700Z

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

danm 2021-03-09T13:59:19.232900Z

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

danm 2021-03-09T14:01:34.235200Z

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

alexlynham 2021-03-09T14:02:40.235300Z

me, coming to after too long working with monadic pipelines

alexlynham 2021-03-09T14:03:43.235600Z

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

mccraigmccraig 2021-03-09T14:12:18.236700Z

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 😬

alexlynham 2021-03-09T14:13:09.236800Z

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

alexlynham 2021-03-09T14:14:11.236900Z

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:)

mccraigmccraig 2021-03-09T14:15:38.237600Z

i would dearly like some static typing sauce on my dynlangs

jiriknesl 2021-03-09T14:20:03.238500Z

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

alexlynham 2021-03-09T14:20:18.238700Z

mmm agreed

alexlynham 2021-03-09T14:20:38.239100Z

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

jiriknesl 2021-03-09T14:21:57.239500Z

What do you use monadic pipelines for?

jiriknesl 2021-03-09T14:23:43.240800Z

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”.

alexlynham 2021-03-09T14:27:59.241Z

we're in typescript, writing serverless/aws lambdas

mccraigmccraig 2021-03-09T14:28:25.241500Z

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

alexlynham 2021-03-09T14:28:52.242100Z

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

jiriknesl 2021-03-09T14:28:54.242200Z

Monadic pipelines in TS?

mccraigmccraig 2021-03-09T14:29:22.242800Z

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

alexlynham 2021-03-09T14:29:28.242900Z

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

mccraigmccraig 2021-03-09T14:30:16.243500Z

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

mccraigmccraig 2021-03-09T14:30:48.243900Z

i'm working on our nextgen stuff atm - https://github.com/mccraigmccraig/promisefx

jiriknesl 2021-03-09T14:38:16.245100Z

> 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.

alexlynham 2021-03-09T14:42:49.245200Z

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);
  }
}; 

alexlynham 2021-03-09T14:43:05.245300Z

where the const response line is where the magic happens

alexlynham 2021-03-09T14:45:27.245400Z

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> =>
  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 }; });

alexlynham 2021-03-09T14:46:57.245500Z

https://gist.github.com/the-frey/ad36baa1e86c4669e9ab45f43326e5a0 <--- ^ this example w/syntax highlighting

mccraigmccraig 2021-03-09T14:48:44.246300Z

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

alexlynham 2021-03-09T14:51:23.246400Z

ikr

alexlynham 2021-03-09T14:51:40.246500Z

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

alexlynham 2021-03-09T14:52:46.246600Z

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

alexlynham 2021-03-09T14:53:18.246800Z

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

mccraigmccraig 2021-03-09T15:25:35.248300Z

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 ?

alexlynham 2021-03-09T15:26:55.248400Z

iirc there's a gotcha

alexlynham 2021-03-09T15:27:15.248500Z

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

alexlynham 2021-03-09T15:27:35.248600Z

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

mccraigmccraig 2021-03-09T15:29:19.249100Z

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

alexlynham 2021-03-09T15:30:40.249200Z

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

alexlynham 2021-03-09T15:31:00.249300Z

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

alexlynham 2021-03-09T15:31:16.249400Z

although maybe i'm misremembering

alexlynham 2021-03-09T15:31:20.249500Z

:woman-shrugging:

mccraigmccraig 2021-03-09T15:56:05.254800Z

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

mccraigmccraig 2021-03-09T15:56:42.255200Z

alet in funcool/cats does the same thing - https://github.com/funcool/cats/blob/master/src/cats/core.cljc#L413

alexlynham 2021-03-09T16:02:59.255300Z

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