I ran into a beginner mistake that I was unable to figure out. So I posted to PureScript Discord and the conversation quickly showed me not only why what I did didn't work, but suggestions for how to fix it specifically. Kudos to @monoidmusician and @natefabion. Here's the discussion:
I have a compiler error that I'm just not understanding why it's happening: Here's the test code:
module Foo where
import Data.Generic.Rep (class Generic)
import Data.List (List(..), filter, length, null)
import Data.List.Types ((:))
import Data.Show (class Show)
import Data.Show.Generic (genericShow)
newtype Foo = Foo { bars :: List Bar }
derive instance Generic Foo _
instance Show Foo where
show = genericShow
newtype Bar = Bar { bar :: Int }
derive instance Generic Bar _
instance Show Bar where
show = genericShow
In repl
, I populate instances of Foo
and Bar
:
> import Foo
> bar = Bar { bar : 1 }
> foo = Foo { bars : ( bar : Nil ) }
> foo
(Foo { bars: ((Bar { bar: 1 }) : Nil) })
So far, so good. I'm seeing what I expect.
But, when I attempt to access bars
from foo,
I get the TypesDoNotUnify
error as follows:
> foo.bars
Error found:
in module $PSCI
at :1:1 - 1:4 (line 1, column 1 - line 1, column 4)
Could not match type
{ bars :: t0
| t1
}
with type
Foo
while checking that type Foo
is at least as general as type { bars :: t0
| t1
}
while checking that expression foo
has type { bars :: t0
| t1
}
while checking type of property accessor foo.bars
in value declaration it
where t0 is an unknown type
t1 is an unknown type
See https://github.com/purescript/documentation/blob/master/errors/TypesDoNotUnify.md for more information,
or to contribute content related to this error.
>
Why does foo.bars
not work?
A newtype is an opaque nominal type. It's semantically equivalent to a data
declaration. With data/newtype you are declaring a constructor, and to access the value you must pattern match on it. Foo
is not a record, and so doesn't support dot syntax. It's a constructor around a record, and you need to unwrap it first before you can invoke .bars
. Dot syntax only works on things of type Record ...
or { ... }
(which is sugar for Record
).
@natefaubion Appreciate the quick response. Unfortunately, after hours I'm still not getting anywhere. Instead of foo.bars
, which doesn't work, what syntax should I use to retrieve the bars
field of the foo
instance of Foo
? Can you point me to the PureScript documentation where this particular feature I'm trying to implement is described?
@oldfartdeveloper here’s four comparable options (the fourth requires the extra import and instance):
(\(Foo f) -> f.bars) foo
case foo of Foo f -> f.bars
let Foo f = foo in f.bars
import Data.Newtype (class Newtype, unwrap)
derive instance Newtype Foo _
(unwrap foo).bars
The first three are all different ways of pattern matching to unwrap the constructor, and the fourth is a magic typeclass instance to do the unwrapping with a generic function.
@monoidmusician Thank you. I'm just attempting to go down the 4th option. This really helps!
happy to help 😊
pattern matching is one of my favorite features of PureScript and other languages: you can deconstruct data using almost the same syntax for constructing it!
Yeah, I need to practice it. I appreciate it but sometimes I don't recognize the opportunity.
you can also deconstruct record literals, so you don’t even need the .bar
accessor:
case foo of Foo { bar: b } -> b
-- with record puns:
case foo of Foo { bar } -> bar
you can also write getBarsFromFoo (Foo f) = f.bars
Here are your 5 options:
getBarsFromFoo :: Foo -> List Bar
getBarsFromFoo foo = (\(Foo f) -> f.bars) foo
getBarsFromFoo2 :: Foo -> List Bar
getBarsFromFoo2 foo = case foo of Foo f -> f.bars
getBarsFromFoo3 :: Foo -> List Bar
getBarsFromFoo3 foo = let Foo f = foo in f.bars
getBarsFromFoo4 :: Foo -> List Bar
getBarsFromFoo4 foo = (unwrap foo).bars
getBarsFromFoo5 :: Foo -> List Bar
getBarsFromFoo5 (Foo f) = f.bars
I'm publishing this because I figure someone else will run into the same mistake of attempting to directly acceess a record field on a newtype (i.e. foo.bars
).
The volunteers in the PureScript community are the most responsive and helpful of any of the software communities I've encountered over the many decades I've been a programmer. I waited too long to go for help on this, and I spun in the weeds for hours until I finally decided to create the test case and submit it. I'm so glad I did.
Finally, I've attached the test case, Foo.purs
, if you'd like to download and try it for yourself.
- This was done on
purescript
version 0.14.5
Renamed README to make it show first