Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eff monad demands row with Debug.Trace.Trace

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.

like image 229
Fresheyeball Avatar asked Jul 16 '14 14:07

Fresheyeball


1 Answers

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 lifting 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": {} }
like image 83
Phil Freeman Avatar answered Oct 13 '22 04:10

Phil Freeman