well-typed / optics

Optics as an abstract interface
375 stars 24 forks source link

Unwrapping newtype should be easier than `coercedTo`. #484

Open amano-kenji opened 1 year ago

amano-kenji commented 1 year ago

A minimal example of unwrapping a newtype

{-# LANGUAGE TypeApplications #-}
import Optics.Core

newtype Index = Index Int deriving (Show, Eq)

ix :: Index
ix = Index 3

ix2 = ix & coercedTo @Int %~ (*2)

@Int bugs me. When I unwrap a newtype, I shouldn't have to specify the wrapped type.

coerced often fails to infer the right types without explicit type declarations.

arybczak commented 1 year ago
newtype N1 = N1 Int deriving newtype Num
newtype N2 = N2 N1 deriving newtype Num

n2 :: N2
n2 = N2 $ N1 3

hmm = n2 & coercedTo %~ (*2)

Now, does coercedTo coerce N2 to N1 or Int?

amano-kenji commented 1 year ago
hmm = n2 & coercedTo %~ (*2)

<interactive>:9:12: error:
    • Couldn't match representation of type ‘Integer’
                               with that of ‘Int’
        arising from a use of ‘coercedTo’
    • In the first argument of ‘(%~)’, namely ‘coercedTo’
      In the second argument of ‘(&)’, namely ‘coercedTo %~ (* 2)’
      In the expression: n2 & coercedTo %~ (* 2)

I didn't really think about nested newtypes. But, there is Control.Lens.Wrapped which I used when I used lens.

phadej commented 1 year ago

That uses ExtendedDefaultRules enabled in ghci, (EDIT: Which defaults the type of 2 to be Integer). In source files the error will be different (and equally or more unhelpful).

adamgundry commented 1 year ago

I don't think there's any fundamental reason why we couldn't port Control.Lens.Wrapped, it's just that nobody has felt the need to. Unwrapping a single layer of a newtype is a reasonable request, it just isn't what coerced provides.

phadej commented 1 year ago

I think it's not worthy copying Wrapped.

Firstly, it's not just newtype unwrapper, there is

instance Wrapped (Seq a) where
  type Unwrapped (Seq a) = [a]

for example.

Secondly, there is https://hackage.haskell.org/package/newtype and https://hackage.haskell.org/package/newtype-generics which kind of do similar thing, but interestingly there is no newtype-coerce building on top of Coercible.

I don't think that yet another take should be added to optics. So far we mostly successfully avoided redoing things (the Ix / At being the only exception can think of, but that has a reason - it's very optics specific take).