I didn’t write cradle, so I can’t speak authoritatively about original motivation here, but I did use process
a lot, typed-process
for a while too (though had to switch back to process after hitting these bugs.), and now cradle
, so I can compare them as a user.
I really enjoy working with cradle - it feels really ergonomic. I thought from the blog post that it’d be clear to most people that the ways in which that’s case, compared to process
, are much the same as to typed-process
, but apparently I was wrong. In particular:
-
You have a ton of different functions for each use case - reading just stdout, stdout and stderr, stdout and stderr but throwing exceptions… And then start
and with
versions for everything…
-
If you want to come up with another abstraction (such as the timing
one in the blog post), you need to write a new function for each existing function. I find it really cool that with cradle
you can write these, and it’s so trivial to use that functionality. Timing
was one example, but others that come to mind:
-
newtype Json a: (Json MyType) <- run & cmd "foo"
parses stdout with FromJSON
and throws otherwise
-
newtype TempStdoutFile: (TempStdoutFile f) <- run & cmd "foo"
creates a temp file, pipes stdout there, and returns the filepath (this needs a change that should be upcoming, though).
-
data SourceError = SourceError { file: FilePath, lineno: Int, colno: Int, msg: Text} (e :: SourceError) <- run & cmd "foo"
parses stderr, using knowledge about common executables (ghc, gcc, cargo, etc) error formats, defaulting to maybe the GNU-style.
I can’t really figure out how to replicate Timing
, for example, in process
/typed-process
without either having a ton of new functions, or only supporting some of their APIs.
To reiterate, these combine simply:
(Exit e, Json parse, t :: Timing, s :: [SourceError]) <- run "nix" & addArgs ["build", ".#", "--json"]
Does the right thing. (In fact, even multiple uses of the same handle will work!). Of course, all of this is extra fancy stuff. 95% of the time it’s (StdoutTrimmed out) <- run "ls"
or the like.
- There’s the mostly aesthetic fact that a lot of functions ignore
ProcessConfig
parameters.
I don’t think any of these are really different from process
and typed-process
?
Regarding streaming and with*
. As mentioned in the blog post, cradle generally has a compositional approach. If there’s already the wonderful async
library (and the simple forkIO
), that should be enough (we’ve definitely been using cradle
that way.). And I already mentioned above the reasoning for not having shell
, and how you can still mimic it pretty easily if you really want to.
Regarding type signatures: very rarely do you need them! I gave them in the blog post and above so the lines are self-contained and standardized. But of course the majority of the times where I gave them, type inference would quickly have settled things. E.g.:
run "cmd" >>= \case
ExitSuccess -> doSuccess
ExitFailure _ -> doFailure
In short, I definitely enjoy using cradle
a lot more than I did typed-process
. But of course, these are my own experiences. If you’re happy with typed-process
I wouldn’t want to take that away from you, just like presumably typed-process
shouldn’t take anything away from the people who were happy with process
!