Reputation: 129
I have created an extendable record type and how can I return the minimal base type without the compiler throwing error.
I have a record with type,
type SectionContent r =
{ titleText :: String
, subTitleText :: String
, ....
| r
}
I'm Extending it, like
type MyConfig =
{ section1 : SectionContent ()
, section2 : SectionContent (someContent :: {...})
..
}
When I try to access in my code like this,
-- This is inside another function & I have access to config
currenScreenConfig :: forall r. SectionContent r
currenScreenConfig = if predicate
then config.section1
else config.section2
It's throwing error,
Could not match type
( someContent :: {...})
with type
r0
.....
where r0 is a rigid type variable
bound at ...
....
And what exactly rigid type mean here?
Upvotes: 0
Views: 26
Reputation: 80880
This is a classic confusion about who gets to choose the generic parameters.
When you access (or "reference", or "use") a value whose type has a forall r.
in front, you get to choose what r
is. And whoever implemented that value must make it such that it would work with the r
you choose. Without knowing what you will choose in advance.
In other words: it's the caller of the function that gets to choose type parameters, not the implementer.
Therefore, somebody calling your currenScreenConfig
function later might decide to choose r ~ ( foo :: String )
. And then your function would have to return a record SectionContent ( foo :: String )
.
And another caller, in a different place, might choose ( bar :: Int )
, and then your function would have to return a record SectionContent ( bar :: Int )
.
Do you see how there is no way to implement such function?
Now, if you want to return just SectionContent ()
, then that's what the type of the function should be:
currenScreenConfig :: SectionContent ()
But of course with that you can't return config.section2
, because it has a different type.
There are ways to "trim" a record (i.e. throw away unneeded fields). One such way is the pick
function from the record-extra
package, which you could use like this:
currenScreenConfig :: SectionContent ()
currenScreenConfig = if predicate then config.section1 else pick config.section2
However, I encourage you to employ a more natural model instead. If the "common" part of the various screen contents is commonly extracted like that, it would be more natural to include it as a field rather than merge:
type SectionContentCommon =
{ titleText :: String
, subTitleText :: String
, ....
}
type SectionContent r =
{ common :: SectionContentCommon
| r
}
type MyConfig = ... same ...
currenScreenConfig :: SectionContent ()
currenScreenConfig = if predicate then config.section1.common else config.section2.common
It may seem cool and shiny to use extensible records, but I strongly encourage you to consider a more straightforward model instead. Trust me: cool and shiny never outweighs long-term maintenance.
Upvotes: 3