0

This is code is a working but simplified example of what I want to accomplish. I want to map from one type wrapping records into another:

import Prelude
import Data.Lens
import Data.String as String

newtype AsString = AsString { names :: Array String }
newtype AsSize   = AsSize   { names :: Array Int }

_names = lens _.names (_ { names = _ })

to_sizes :: AsString -> AsSize
to_sizes (AsString s) = AsSize $ over (_names <<< traversed) String.length s

How do I do the same with lenses only and without unwrapping the record first?

When using a lens like lens (\(AsString s) -> s) (const AsString) for the original type I guess it expects the result to be of the original type?

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
DerKastellan
  • 143
  • 2
  • 7

2 Answers2

0

I guess I found an answer through experimentation:

import Prelude
import Data.Lens
import Data.String as String

newtype AsString = AsString { names :: Array String }
newtype AsSize   = AsSize   { names :: Array Int }

_ToSize = lens (\(AsString s) -> s) (const AsSize)
_names  = lens _.names (_ { names = _ })

to_sizes :: AsString -> AsSize
to_sizes s = over (_ToSize <<< _names <<< traversed) String.length s

The (const AsSize) seems to "mirror" the original record into the new type.

DerKastellan
  • 143
  • 2
  • 7
0

I'm not sure if you would be happy about this answer as I'm also proposing a bit of refactoring here... If you are not happy please let me know and I will try to provide code sample based on below lenses without touching your types ;-)

Are you able to accept changing AsString and AsSize as follows?

newtype Names a = Names { names :: Array a }

type AsString = Names String
type AsSize = Names Int

This refactoring will simplify operations a bit and makes your type a bit more reusable. I really recommend this talk about the power of type parameters: https://www.youtube.com/watch?v=BHjIl81HgfE).

For names field we can create lens by using generic prop function.

Regarding Name type we should derive Newtype instance at first (please notice quite specific syntax of this deriving - I think that _ type compiler deduces by itself):

 newtype Names a = Names { names :: Array a }
 derive instance newtypeNames ∷ Newtype (Names a) _

This class provides wrap and unwrap methods which are used by _Newtype lens. So we can just use _Newtype lens now.

Finally you should be able to compose these two. Here we go:

module Main where

import Prelude

import Data.Lens (over, traversed)
import Data.Lens.Iso.Newtype (_Newtype)
import Data.Lens.Record (prop)
import Data.Newtype (class Newtype)
import Data.String as String
import Type.Prelude (SProxy(..))

newtype Names a = Names { names :: Array a }
derive instance newtypeNames ∷ Newtype (Names a) _

type AsString = Names String
type AsSize = Names Int

_names  = prop (SProxy ∷ SProxy "names")

toSizes :: AsString -> AsSize
toSizes = over (_Newtype <<< _names <<< traversed) (String.length)

P.S.

I often write also this to simplify type inference if I'm modifying the same type:

_Newtype' ∷ ∀ s a. (Newtype s a) ⇒ Iso' s a
_Newtype' = iso unwrap wrap
paluh
  • 2,171
  • 20
  • 14
  • Thank you. I was thinking about doing it like this, it's great to have an example as I was playing with `Generic` and `derive instance` today. I have yet to learn what SProxy does and you kinda lost me on the PS, but this is very valuable learning material for me. – DerKastellan Nov 07 '18 at 19:22
  • Are you @DerKastellan registered on slack channel #purescript or #purescript-beginners? We can discuss `SProxy` a bit further there - please ping me with direct message if you want ;-) – paluh Nov 07 '18 at 20:20