{"action":"created","issue":{"url":"https://api.github.com/repos/slamdata/purescript-halogen/issues/288","repository_url":"https://api.github.com/repos/slamdata/purescript-halogen","labels_url":"https://api.github.com/repos/slamdata/purescript-halogen/issues/288/labels{/name}","comments_url":"https://api.github.com/repos/slamdata/purescript-halogen/issues/288/comments","events_url":"https://api.github.com/repos/slamdata/purescript-halogen/issues/288/events","html_url":"https://github.com/slamdata/purescript-halogen/issues/288","id":140081625,"number":288,"title":"Simplified component signatures","user":{"login":"natefaubion","id":144058,"avatar_url":"https://avatars.githubusercontent.com/u/144058?v=3","gravatar_id":"","url":"https://api.github.com/users/natefaubion","html_url":"https://github.com/natefaubion","followers_url":"https://api.github.com/users/natefaubion/followers","following_url":"https://api.github.com/users/natefaubion/following{/other_user}","gists_url":"https://api.github.com/users/natefaubion/gists{/gist_id}","starred_url":"https://api.github.com/users/natefaubion/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/natefaubion/subscriptions","organizations_url":"https://api.github.com/users/natefaubion/orgs","repos_url":"https://api.github.com/users/natefaubion/repos","events_url":"https://api.github.com/users/natefaubion/events{/privacy}","received_events_url":"https://api.github.com/users/natefaubion/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":2,"created_at":"2016-03-11T04:19:00Z","updated_at":"2016-03-11T12:07:11Z","closed_at":null,"body":"Right now, state is totally opaque to the outside world, yet we still have to thread around the composed type. Additionally, parent eval functions have to thread around the child state even though nothing can be done with it. If we want to commit to this route, with a small change we can eliminate a lot of the type boilerplate. The following is essentially the same thing we do now, but because we move the initial state into the component definition, we no longer have to deal with threading the type around, so we can focus on the only interface we actually expose: the query algebra.\r\n\r\n```purescript\r\ntype Render s f g =\r\n  { state :: s\r\n  , hooks :: Array (Hook f g)\r\n  , tree  :: Tree f Unit\r\n  }\r\n\r\n-- By moving `initialState` to the component spec we can existentially hide it.\r\n-- Components should be parameterized if they need to take a \"custom\" initial state.\r\ntype ComponentImpl s f g =\r\n  { render :: s -> Render s f g\r\n  , eval :: Natural f (ComponentDSL s f g)\r\n  , initialState :: s\r\n  , initializer :: Maybe (f Unit)\r\n  , finalizers :: s -> Array (Finalized g)\r\n  }\r\n\r\nforeign import data Component :: (* -> *) -> (* -> *) -> *\r\n\r\nmkComponent :: forall s f g. ComponentImpl s f g -> Component f g\r\n\r\nrunComponent :: forall f g r. (forall s. ComponentImpl s f g -> r) -> Component f g -> r\r\n\r\ntype InstalledImpl s f g =\r\n  { component :: ComponentImpl s f g\r\n  , state :: s\r\n  , memo :: Maybe (Unit -> Tree f Unit)\r\n  }\r\n\r\nforeign import data Installed :: (* -> *) -> (* -> *) -> *\r\n\r\nmkInstalled :: forall s f g. InstalledImpl s f g -> Installed f g\r\n\r\nrunInstalled :: forall f g r. (forall s. InstalledImpl s f g -> r) -> Installed f g -> r\r\n\r\nnewtype ParentState s f' p g = ParentState\r\n  { state :: s\r\n  , children :: Map p (Installed f' g)\r\n  }\r\n\r\ntype ComponentHTML f = HTML Void (f Unit)\r\n\r\ntype ComponentDSL s f g = Free (HalogenF s f g)\r\n\r\ntype ParentQuery f f' p = Coproduct f (ChildF p f')\r\n\r\ntype ParentHTML f f' p g = HTML (Slot f' p g) (f Unit)\r\n\r\ntype ParentDSL s f f' p g = Free (HalogenFP ParentEventSource s f (QueryF s f' p g))\r\n\r\ntype QueryF s f' p g = Free (HalogenF (ParentState s f' p g) (ChildF p f') g)\r\n\r\ndata Slot f p g = Slot p (Component f g)\r\n\r\ndata ChildF p f i = ChildF p (f i)\r\n\r\n---\r\n\r\ndata Query\r\n\r\nsomeComponent :: Component Query (Aff ...)\r\nsomeComponent = component { render, eval, initialState } where\r\n\r\n  initialState :: Int\r\n\r\n  render :: Int -> ComponentHTML Query\r\n\r\n  eval :: Natural Query (ComponentDSL Int Query (Aff ...))\r\n\r\n\r\ndata PQuery\r\n\r\nanotherComponent :: Component (ParentQuery PQuery Query Int) (Aff ...)\r\nanotherComponent = parentComponent { render, eval, initialState } where\r\n\r\n  initialState :: String\r\n\r\n  render :: String -> ParentHTML PQuery Query Int (Aff ...)\r\n\r\n  eval :: Natural PQuery (ParentDSL String PQuery Query Int (Aff ...))\r\n```\r\n\r\nSo basically, the user doesn't ever have to know or write `ParentState`. Parent eval's have the expected type: parent state, parent query, child query, slot, base functor. Is this a direction we want to move in, or do we want to figure out how we can open up state more? If we did this #221 would become impossible."},"comment":{"url":"https://api.github.com/repos/slamdata/purescript-halogen/issues/comments/195341930","html_url":"https://github.com/slamdata/purescript-halogen/issues/288#issuecomment-195341930","issue_url":"https://api.github.com/repos/slamdata/purescript-halogen/issues/288","id":195341930,"user":{"login":"jdegoes","id":156745,"avatar_url":"https://avatars.githubusercontent.com/u/156745?v=3","gravatar_id":"","url":"https://api.github.com/users/jdegoes","html_url":"https://github.com/jdegoes","followers_url":"https://api.github.com/users/jdegoes/followers","following_url":"https://api.github.com/users/jdegoes/following{/other_user}","gists_url":"https://api.github.com/users/jdegoes/gists{/gist_id}","starred_url":"https://api.github.com/users/jdegoes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdegoes/subscriptions","organizations_url":"https://api.github.com/users/jdegoes/orgs","repos_url":"https://api.github.com/users/jdegoes/repos","events_url":"https://api.github.com/users/jdegoes/events{/privacy}","received_events_url":"https://api.github.com/users/jdegoes/received_events","type":"User","site_admin":false},"created_at":"2016-03-11T12:07:11Z","updated_at":"2016-03-11T12:07:11Z","body":"I agree that hiding state is a natural stepping stone on the current path, and will simplify a lot of types; so long as a component always has its initial state, then in theory, the state can be existentially hidden. I do believe that some of the embedding code might have to rely on coercion, simply because the type system isn't strong enough to make the kinds of guarantees we need; but I also think the code could be (manually) proved to be safe.\r\n\r\nMy only objection is that it's incompatible with #221. \r\n\r\nThe main goals of #221 are as follows:\r\n\r\n1. Allow parents to easily access children state, which eliminates a lot of unnecessary querying (you achieve this by passing in a child selector as well as the parent's nest);\r\n2. Eliminate the messiness and deep nesting of coproduct hierarchies, and replace it with a much flatter, user-defined query algebra, whose individual terms are projected via prisms.\r\n\r\nI think (2) can still be achieved with hiding the state, but definitely not (1). \r\n\r\nBut, that said, current Halogen probably doesn't want you to have direct access to the state (across component boundaries), because it's used (or can be used) for caching the view. This sometimes leads to much more complex code, with parents querying to get the child state when they could simply look at it instead, but of course achieves stronger encapsulation and may benefit from caching.\r\n\r\nSo I think it could go either way. It comes down to, What does current Halogen really want to be?"}}