Disfunctional RSS

Where misquotes, misinformation, and misspellings occur daily.

Archive

Jul
19th
Fri
permalink

JS Ei-ei-O

What follows is a debatable idea, but it’s fun so whatevs yall, whatevs


I’m probably talking about the IO monad, but we’ll pretend I’m not cause I don’t want to start shit.

Let’s take a normal function and alter it ever so slightly. We’ll work with focus.

  //+ focus :: Fireable a => a -> undefined
  var focus = function(element) {
    element.fireEvent('focus');
  }
Now change it to:

  var UI = Constructor(function(val) { this.val = val }, {deriving: Functor})
 
  //+ focus :: Fireable a =>  a -> UI(a)
  var focus = function(element) {
    element.fireEvent('focus');
    return UI(element);
  }
What have we done here?*

We took a function that returned undefined and updated it to return a UI functor with the original element inside. The UI functor does nothing but hold the element inside - it’s sole purpose is to “flag” that the side effect happened.

Already there are a few benefits here:

  1. Our element will not get “thrown away” during this action. Though we must fmap over our UI functor to use it, the element is still around if we decide we want to use it with other functions.
  2. We know that some UI action has occurred in our result rather than a useless undefined. This is the big one.


Let’s make our focus function return something a little more expressive.

 
  var Focus = subclass(UI);
 
  //+ focus :: Fireable a =>  a -> Focus(a)
  var focus = function(element) {
    element.fireEvent('focus');
    return Focus(element);
  }
Now we will know a Focus has happened somewhere in our program rather than just some UI effect.

Example


Let’s look at a short, but full example. Say we have a view that consists of a search bar on top and a table below. Let’s assume we just got movies from an api call and we want to populate the table with them, then focus the search bar so our user can filter them down. Without using our new style (or focus function), here’s a standard way to do just that.

var MoviesController = function(movies) { 
  //+ makeRow :: Movie -> TableRow
  var makeRow = render('MovieRow')

  //+ populateTable :: [Movie] -> undefined
    , populateTable = compose(setData($.table), map(makeRow))

  //+ initScreen :: [Movie] -> undefined
    , initScreen = compose(focus.partial($.search_bar), populateTable)
    ;

   return initScreen(movies);
}
When we run it we get an undefined which is a totally useless result. Now keep in mind, we weren’t going to use the result anyways (what can you do with undefined?) so why not have it give us the skinny on what went down?

Let’s apply our badass new way of inspecting side effects here. But first, one more support function to go along with our new focus function from above.

  var TableFill = subclass(UI);

  //+ setData :: Table -> [TableRow] -> TableFill([TableRow])
  var setData = function(table, rows) {
    table.html(rows);
    return TableFill(rows);
  }.autoCurry();

var MoviesController = function(movies) {
   //+ makeRow :: Movie -> TableRow
   var makeRow = render('MovieRow')

   //+ populateTable :: [Movie] -> TableFill([TableRow])
     , populateTable = compose(setData($.table), map(makeRow))

   //+ initScreen :: [Movie] -> Focus(TableFill([TableRow]))
     , initScreen = compose(focus.partial($.searchbar), populateTable)
     ; 

   return initScreen(movies);
}
Nothing changed except our support UI functions and the type signatures. Now when we run this controller we will get back: Focus(TableFill([TableRow])).

Kick ass! Because of the nesting and specific names for our UI functors we can see that first a table load happened, then a focus.

It’s important to note this is not stopping any side effects. We’re not lazy, we’re not pure…yet anyways. What we’re interested in here is seeing what side effects actually occurred during our run - and we do see them all, plain as day, in the order they occurred. Mission accomplished.

It seems useful enough to have a kind of “side effect audit” for debugging reasons alone, but what other ways can we take advantage of this new information?

Use Cases


Testing comes to mind:


it("focuses and loads the table", function(){
  expect(MoviesController([]).toEqual(Focus(TableFill([])))
});

In this test, we assert that the side effect structure we’ve accumulated matches what we’d expect. We’ve just programmatically asserted things happened on the screen. That is rather convenient because it spares us the horror of working with those sluggish abominations which actually interact with UI during test runs.

We can also inspect transitory effects throughout the sequence of events. In addition to that, we no longer have to think of those creative, awkward ways to assert end state or fiddle around with mocks with expectations. Expectations on mocks seemed to be a necessary evil which pushed you to test direct implementation rather than interface. No more.

Something interesting here is our UI containers are just functions. In other words, Focus() is just a constructor. That means we can build some common side effect sequences. Here’s a useful compound event:

var RiseOnToScreen = compose(UpAnimation, AppendView)

//+ showView :: View -> RiseOnToScreen(View)
expect(showView(view)).toEqual(RiseOnToScreen(view))
So…we’re using functors right? Let’s make them work for us. If we have a removed element, we might not want run functions on it:

  //+ remove :: View -> View -> Removed(View)
  var remove = function(view, child_view){
    view.remove(child_view);
    return Removed(child_view);
  }.autoCurry();
 
  Functor(Removed, {
    fmap: function(f){
      return this; // don't run if it's already removed.
    }
  });
Hey, you wanna get nuts? Let’s get nuts.

Getting Nuts


Say we wanted to be pure/lazy. With the nested type structure we return, we have all the instructions, in order, right? Let’s alter our UI type just a bit to store the function too:

var UI = Constructor(function(val, func) {
  this.val = val;
  this.func = func;
});
Functor(UI, {
  fmap: function(f){
    return UI(this.val, compose(f, this.func));
  }
});
And viola! We’ve got a full UI action that encodes it’s function, but doesn’t run yet.

The only missing piece is a mechanism to run the side effects when the time is right. Let’s call it runUI. In there, we could just run each action or perhaps we could inspect each one and decide what to do. We could perform optimizations (1 reflow?) or serialize it. Sky’s the limit with this IO stuff. Whoops, did I say IO, I meant UI…

***By the way, I’m updating the https://github.com/loop-recur/typeclasses this week if you want to help. I’ve already got deriving working too!
blog comments powered by Disqus