Search code examples
purescript

How to correctly unwrap `data` inside `data`?


I’m trying to access nested data (Foo.y inside Bar in the example below), but the straightforward approaches for unwrapping Foo inside Bar that come to mind do not work. But how to unwrap it correctly?

Here my data:

module Foo where

import Prelude

data Foo = Foo { y :: Int }

data Bar = Bar { x   :: Int
               , foo :: Foo }

The following (of course) does not compile, error is Could not match type { y :: Int } with type Foo — just like Bar, Foo needs unwrapping first:

fn1 :: Bar -> Int
fn1 (Bar { x, foo }) = x + foo.y

So I put up my hopes for the following, but alas, compiler says “no” (parentheses around Foo constructor don’t help):

fn2 :: Bar -> Int
fn2 (Bar { x, Foo { y } }) = x + y

fn3 :: Bar -> Int
fn3 (Bar { x, Foo f }) = x + f.y

The following works, using a helper function to do the unwrapping, but there has got to be a better way:

getY (Foo foo) = foo -- helper function

fn4 :: Bar -> Int
fn4 (Bar { x, foo }) = let foo2 = getY foo in
                       x + foo2.y

So, how do I do nested “unwrap”?

[EDIT]

After an hour or two of trying things out, I came up with this, which works:

fn5 :: Bar -> Int
fn5 (Bar { x, foo = (Foo f) }) = x + f.y

Is this the idiomatic way to do it? Why don’t fn2 and fn3 work?


Solution

  • Functions fn2 and fn3 do not work because the compiler does not know which record field you are referring to (foo). You have to reference record fields by name.

    Function fn4 is a perfectly fine solution (although your naming is pretty confusing, getY actually returns the wrapped record inside the Foo constructor, not the y value).

    As far as I can tell, fn5 is the shortest possible solution. I would personally prefer a helper function (like in your fourth example):

    getY :: Foo -> Int
    getY (Foo rec) = rec.y
    
    fn6 :: Bar -> Int
    fn6 (Bar { x, foo }) = x + getY foo