I am writing bindings from History.js into PureScript and still struggling to understand the Eff monad, what a row of effects are and why they are valuable. Right now I have the following written with EasyFFI
type Title = String
type Url = String
type State = forall a. {title :: Title, url :: Url | a}
type Data = forall a. { | a}
type StateUpdater = Data -> Title -> Url -> Unit
-- this function is a work around for 'data' as a reserved word.
foreign import getData
"function getData(state){ return state['data']; }"
:: forall a b. { | a} -> b
unwrapState :: StateUpdater -> State -> Unit
unwrapState f s = f (getData s) s.title s.url
replaceState' :: StateUpdater
replaceState' = unsafeForeignProcedure ["data","title","url"] "History.replaceState(data,title,url)"
replaceState :: State -> Unit
replaceState = unwrapState replaceState'
foreign import data BeforeEach :: !
beforeEach :: forall e a. Eff e a -> Eff (beforeEach :: BeforeEach | e) Unit
beforeEach = unsafeForeignProcedure ["fn",""] "window.beforeEach(fn);"
Later in the code I have the following:
beforeEach $ do
replaceState {title = "wowzers!", url = "/foos"}
and get the following error
Cannot unify Prelude.Unit with Control.Monad.Eff.Eff u2518 u2517.
I've tried manipulating the type signatures in various ways to try and make it all line up, but I don't really understand what is going wrong. So its just guessing at this point.
My modified version of your code is at the end of this post, but I had to make a few modifications in order to make it compile:
I assume the intention was for your StateUpdater
to have an effect on the browser history, so I changed its type to use the Eff
monad with a new History
effect type. This was the main problem, since your last line used replaceState
whose result type is not in the Eff
monad. This resulted in the type error you saw.
I moved some of the universally quantified type variables in your type synonyms into function types. I also removed your Data
type synonym and moved the data content into a new data
field in the State
type.
This is important, because your previous Data
type had no inhabitants. There is a common misconception (for reasons I do not understand) that forall a. { | a}
is a type of records "where I don't care about the fields". That is incorrect - this type represents the type of records which contains all fields which could possibly exist, and such a type is clearly uninhabited. There is an important difference between forall a. {| a} -> r
and (forall a. {| a}) -> r
from the point of view of the caller.
In answer to your original question: "what is a row of effects, and why are they useful?" - rows were originally added to PureScript to deal with polymorphism on record types without having to resort to subtyping. Rows allow us to give polymorphic types to functions which use records, in such a way that we can capture "the rest of the record" as a concept in the type system.
Rows also turned out to be a useful concept when dealing with effects. Just like we don't care what the rest of a record is, we usually don't care what the rest of a set of effects looks like, so long as all effects get propagated correctly in the type system.
To give an example, there are two effects involved in my modified code: History
, and your BeforeEach
. The actions beforeEach
and replaceState
each only use one of these effects, but their return types are polymorphic. This allows the combination of the two functions in main
to have both effects, and type correctly. main
has type forall eff. Eff (history :: History, beforeEach :: BeforeEach | eff) {}
which is the most general type, inferred by the type checker.
In short, rows in the effect system provide a neat way to handle the interleaving of various "native" effects, so that the developer does not have to worry about things like the order of effects or lift
ing computations à la mtl
.
module Main where
import EasyFFI
import Control.Monad.Eff
type Title = String
type Url = String
type State d = { title :: Title, url :: Url, "data" :: { | d } }
type StateUpdater d = forall eff. { | d } -> Title -> Url -> Eff (history :: History | eff) {}
foreign import data History :: !
unwrapState :: forall eff d. StateUpdater d -> State d -> Eff (history :: History | eff) {}
unwrapState f s = f s."data" s.title s.url
replaceState' :: forall d. StateUpdater d
replaceState' = unsafeForeignProcedure ["d","title","url"] "History.replaceState(d,title,url)"
replaceState :: forall eff d. State d -> Eff (history :: History | eff) {}
replaceState = unwrapState replaceState'
foreign import data BeforeEach :: !
beforeEach :: forall e a. Eff e a -> Eff (beforeEach :: BeforeEach | e) {}
beforeEach = unsafeForeignProcedure ["fn",""] "window.beforeEach(fn);"
main = beforeEach $ do
replaceState { title: "wowzers!", url: "/foos", "data": {} }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With