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!