TIL: `join` over `(->)`

By@precioso.designJan 23, 2026

I discovered a neat pattern: using join with the function arrow ((->)) as a Monad. When you join over functions, you get:

join :: (a -> a -> b) -> a -> b
join fn value = fn value value

This lets you pass the same argument twice: once to determine what to do, and another to actually do it.

How I used it

In @wasp.sh, we have validators with the signature type Validator = input -> Validation. Most of our validation helpers are written in a point-free, un-applied style, which works great, except recently when I needed to inspect the input to decide which validator to run.

dependencyValidator :: V.Validator PackageJson
dependencyValidator depName = join $ \pkgJson ->
  V.inField "dependencies" $
    V.inField depName $
      depVersionValidator (requestsLooseChecking pkgJson)

depVersionValidator :: Bool -> V.Validator DependencyVersion
depVersionValidator True = checkExistence `V.and` checkCorrectVersion
depVersionValidator False = checkExistence

The join gives us access to pkgJson to inspect it (via requestsLooseChecking), while still threading it through to the composed validators.