简体   繁体   中英

Compile type error when trying to return Extendable record

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?

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 (ie 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM