Some good answers already, but it's worth mentioning that "effects" are managed by Elm's external machinery (the Elm runtime) and delivered to your app via messages. There's a really well defined event loop and building apps is accomplished in a very boilerplate (read: consistent) manner. When an update occurs, your app responds to a given message which includes the current app state, and it returns a new state. That state is handed back to the runtime (possibly mutated), and whatever modifications you made will come back around in the next update.
http://toreto.re/tea/