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 div
s:
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.