Do Halogen components allow recursion?

Hi All
I’ve been trying to work out how to get a Halogen component to have another instance of itself as a child. (I can easily do this in layout without components, but would like components for user interaction. Currently I’m getting an error messages I can’t work out. From a minimal example which outputs a list as nested divs:

 Could not match type

   ComponentSlot t2 t3 t4

 with type

   w0


while trying to match type HTML (ComponentSlot t2 t3 t4)
 with type HTML w0
while checking that expression (((slot_ _cell) (state.counter)) component) { counter: (...) 1
                                                                          , contents: (...) (...)
                                                                          }
 has type HTML w0 i1
in binding group component

where w0 is a rigid type variable
       bound at (line 58, column 7 - line 67, column 14)
     i1 is a rigid type variable
       bound at (line 58, column 7 - line 67, column 14)
     t4 is an unknown type
     t2 is an unknown type
     t3 is an unknown type

I can’t work out how to rearrange things for the type-checker, so I’m wondering if I’ve missed something, or if the recursive nesting is impossible? I’ve even tried things like mutual recursion to see if I could trick it, but be result was the same.
Thanks in advance, Ben

Yes I wondered if something to do with fixed-points etc might work, but that’s a bit beyond me. Think I’ll have to look into using far fewer components and tracking the ‘nesting’ in normal html producing functions. Oh well.

For what is worth: I did not find the time to check it but from a quick look I think the error at the moment is from inner forall here type Slots = ( cell :: ∀ query. H.Slot query Void Int )

Have you tried what happens when you give an actual query like type Query = Const Void instead of having it generic?


This here at least type-checks / compiles in TryPureScript:

-- Simple demonstration of a recursively nested Halogen component, translating
-- a List into neseted `divs`, the recursive call does not type check.

module Main where

import Prelude

import Data.List (List(..), (:), head, null, tail)
import Data.Maybe (fromJust)
import Effect (Effect)
import Effect.Class (class MonadEffect)
import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.VDom.Driver (runUI)
import Type.Proxy (Proxy(..))
import Partial.Unsafe (unsafePartial)
import Data.Const (Const)

main :: Effect Unit
main = HA.runHalogenAff do
  body <- HA.awaitBody
  runUI component init body

type Query = Const Void

type Slots = ( cell :: H.Slot Query Void Int )

_cell = Proxy :: Proxy "cell"

-- State has a counter to id recursive componets & List of values to be output
type State = { counter :: Int, contents :: List String }

type Input = State

init :: State
init = { counter: 1, contents: ("a" : "b" : "c" : Nil) }

component :: ∀ o m. H.Component Query Input o m
component =
  H.mkComponent
    { initialState
    , render
    , eval: H.mkEval H.defaultEval
    }

  where
  initialState :: Input -> State
  initialState input = input
    
  render :: ∀ action. State -> H.ComponentHTML action Slots m
  render state =
    HH.div_
      [ HH.text $ unsafePartial $ fromJust $ head $ state.contents
      , HH.div_ [ next ]
      ]

    where
    next =
      if null state.contents then
        HH.text "null" else
        --HH.text $ unsafePartial $ fromJust $ head $ state.contents
        HH.slot_
          _cell
          state.counter
          component
            { counter: state.counter + 1
            , contents: unsafePartial $ fromJust $ tail state.contents
            }

sadly it does not show anything but I did not look further into it (sorry)

@CarstenKoenig is correct that your slot types should not have a forall. You need to pick a query for that slot. I think Halogen should change the kind of slots to prevent this from happening (it’s of kind Type due to a pre-PolyKinds interface). Since it’s polymorphic, you can pick anything when embedding it, but you still need to choose.

The reason it still isn’t working is because your unsafePartial code doesn’t do what you think it does, and it throws an exception. If you use exhaustive pattern matching, it will work.

Working Example

2 Likes

Aha, thankyou! At first it didn’t work in the project I originally had the problem with. But almost by accident I realized that a render function had the same problem with a forall that needed changing to an Action type. Thank-you both for pointing that out, & I’ll also be more skeptical with ‘partial’.

So, the next iteration… I now have a problem with recursive calls of a Query. In this minimal version I’ve modified @natefaubion’s example with a Query of the components to return their state along with that of their sub-components. The parent component is just there for the button to trigger the first query, and the result is logged to the console as the easiest way to output it.

This works fine with only one level of queries, (if you remove the references to rest in the last two lines) but with the recursive query it gives an error message which I can’t work out. Is there another fix similar to the one above for this problem?

One other question I have, do component id’s uniquely identify from all the components of the same type across the page, or do they only need to be unique if they share the same parent?

Hi,

There error is here:

rest <- fromMaybe Nil $ H.request _cell counter GetContent

you want

rest <- fromMaybe Nil <$> H.request _cell counter GetContent

(you want to fmap the result of H.request _cell counter GetContent with fromMaybe Nil instead of using the monadic action of H.request ... as an input to fromMaybe Nil)


For the question with the component id’s you are asking for the slot addresses? If so then no they are used to index the right sub-component of a certain type on the current page. It’s not unusual to have those all to be unit :: Unit when you only need the sub-component once.

Ok that makes sense. Now I’m working out how to apply that to the original I’m working on but that shouldn’t be too hard. Thank you.

Yes, I meant ‘slot addresses’ when I wrote ‘component id’. I’m wondering for multiple components of the same type as in that example, whether their slot address, in that case an integer, needs to be unique only to their parent or unique across the whole document they appear in.