Syntactic abstraction usually requires a language change. For example, Python's "with" statement.
In languages with macros, you don't need to wait for anyone to change the language because the entirety of the language is constructed from a few special operators, and you have the ability to continue constructing.
Like Python, Clojure has a "with-open" macro. If Rich hadn't already added it, you could build it yourself:
; Very simplified version
(defmacro with-open [bindings & body]
`(let ~bindings
(try
~@body
(finally (.close ~(first bindings))))))
withFile "/usr/share/dict/words" ReadMode $ \h -> do
contents <- hGetContents h
count (lines contents)
No need for macros for this. Just passing anonymous code blocks easily.
Interestingly, the type of withFile, after its given the filename and filemode args is:
(Handle -> IO a) -> IO a
Which is the type of a CPS'd computation. CPS'd computations are called the Cont monad in Haskell, which is defined as:
data Cont r a = Cont ((a -> r) -> r)
So the above type of withFile can be written as:
Cont (IO a) Handle
And if we have, for example, multiple resources we're bracketing over, we can represent them as multiple Cont values. Then we can monadically compose them, which is equivalent to Python's "nested" function (Except we also have type safety).
My comment intended to show how macros can allow one to create syntactic abstraction. That one can accomplish X without creating new syntactic abstraction, or that some language already has syntactic abstraction for X, is wholly irrelevant.
In languages with macros, you don't need to wait for anyone to change the language because the entirety of the language is constructed from a few special operators, and you have the ability to continue constructing.
Like Python, Clojure has a "with-open" macro. If Rich hadn't already added it, you could build it yourself:
Thus this: Expands into this: