Hey Matt,
So, that is a great question that I have to admit I don't fully know to, in that I've not done much in ML. My gut says, yes this should be possible. There was actually a fellow at Tufts who started writing an SML implementation of the nanopass framework for a project he was working on. We exchanged emails back in January of 2016, but I've not heard back from him lately, so I'm not sure how far he got.
There was also a start to a Haskell nanopss framework that Micheal Adams and I started at ICFP in 2015 (when I say we started it, I mean he wrote the code and I gave a lightning talk at Haskell symposium). I know he had a student at Utah that picked it up for a time, but I've also not heard much about that. This was using Template Haskell though, so there was some syntactic extension involved.
I also know that there was a Haskell project, and I'm afraid I couldn't find it with a quick search, that was essentially building the boilerplate code for a function that allowed you to transform one tree structured ADT into a similar tree structured ADT, where you only needed to provide functions for the parts that changed. This also felt very nanopass-y to me, though it may have also been using some syntactic extension in Template Haskell, I don't remember for sure.
Working SML of course gets you two of the things that the nanopass framework provides in Scheme: simple ADT syntax (the nanopass framework's languages) and nice pattern matcher (the syntax that the nanopass framework uses for defining passes). I know OCAML (and may SML) even provides a method for defining a new ADT from an existing one through extension with new forms... and maybe removal? I don't remember exactly. So, I think the boilerplate code of recurring through the data structure is really the only remaining part.
I'm not sure if that helps, but I've not really tried to do this in a language where I didn't have a syntactic extension capability.
-andy:)