Wiederverwendung von MArray-Instanzen für einen neuen Typ

8

Ich habe ein Dutzend solcher Newtypes:

newtype MyBool = MyBool Bool
newtype MyInt  = MyInt  Int

Ich möchte vorhandene Instanzen wiederverwenden:

instance MArray IOUArray Int IO         where ...
instance MArray (STUArray s) Int (ST s) where ...

Das Implementieren dieser Instanzen und des gesamten Boilerplate-Codes ist das Letzte, was ich möchte.

Ich habe etwas gefunden, das dem sehr nahe kommt, was ich erreichen möchte:

{-# LANGUAGE GeneralizedNewtypeDeriving, StandaloneDeriving #-}

deriving instance MArray IOUArray MyInt IO      
deriving instance MArray (STUArray s) MyInt (ST s)  

Es schlägt jedoch fehl mit:

Can't make a derived instance of MArray IOUArray MyInt IO
    (even with cunning GeneralizedNewtypeDeriving):
    cannot eta-reduce the representation type enough
In the stand-alone deriving instance for MArray IOUArray MyInt IO

Wie funktioniert das?

Wenn nicht möglich, was ist der am wenigsten schmerzhafte Weg, um diese Instanzen zu erhalten?

oshyshko
quelle
2
Ich habe versucht, die Instanz mit sicheren Zwängen zu generieren, aber leider haben wir dies, type role IOUArray nominal nominalsodass wir nicht von Array-of-Int zu Array-of-Myint zwingen können. (Ich frage mich, warum wir solche Rollen haben.)
Chi
2
Anscheinend wurde dies teilweise so gemacht, dass neue Typen eine andere StorableInstanz für ihre Darstellungen ohne Box verwenden konnten (z. B. die Verwendung einer IntGanzzahl , die kürzer als eine Ganzzahl ist, um sie ohne Box zu speichern newtype Age = Age Int).
KA Buhr

Antworten:

3

Aus der Dokumentation :

Wir können sogar Instanzen von Mehrparameterklassen ableiten, vorausgesetzt, der Newtype ist der letzte Klassenparameter.

Beachten Sie auch, dass die Reihenfolge der Klassenparameter wichtig wird, da wir nur Instanzen für die letzte ableiten können. Wenn die StateMonadobige Klasse stattdessen definiert wäre als

class StateMonad m s | m -> s where ...

dann hätten wir keine Instanz für den Parserobigen Typ ableiten können . Wir nehmen an, dass Multiparameterklassen normalerweise einen „Hauptparameter“ haben, für den das Ableiten neuer Instanzen am interessantesten ist.

Da der letzte Klassenparameter in Ihrem Fall nicht Int/ MyInt, sondern IO/ ist ST s, haben Sie GeneralizedNewtypeDerivingleider kein Glück damit .

Joseph Sible-Reinstate Monica
quelle
1

Okay, Sie stecken hier irgendwie fest, weil einige Designentscheidungen im arrayPaket es schwierig gemacht haben, aber hier ist ein Ansatz, der helfen kann, die Boilerplate zu minimieren.

Sie können eine Typfamilie einführen, um Ihre neuen Typen der zugrunde liegenden Darstellung zuzuordnen:

type family UType e where
  UType MyBool = Bool
  UType MyInt = Int
  UType a = a    -- default for built-in types

und dann neue Typvarianten der Typen IOUArrayund STUArrayArray einführen :

newtype NTSTUArray s i e = NTSTUArray (STUArray s i (UType e))
newtype NTIOUArray i e = NTIOUArray (IOUArray i (UType e))

und verwenden Sie DIESE , um geeignete MArrayInstanzen für Ihre neuen Typen zu erhalten:

instance (MArray (STUArray s) (UType e) (ST s), Coercible e (UType e))
       => MArray (NTSTUArray s) e (ST s) where
  getBounds (NTSTUArray arr) = getBounds arr
  getNumElements (NTSTUArray arr) = getNumElements arr
  newArray (a,b) e = NTSTUArray <$> newArray (a,b) (coerce e)
  newArray_ (a,b) = NTSTUArray <$> newArray_ (a,b)
  unsafeNewArray_ (a,b) = NTSTUArray <$> unsafeNewArray_ (a,b)
  unsafeRead (NTSTUArray arr) i = coerce <$> unsafeRead arr i
  unsafeWrite (NTSTUArray arr) i e = unsafeWrite arr i (coerce e)

instance (MArray IOUArray (UType e) IO, Coercible e (UType e))
       => MArray NTIOUArray e IO where
  getBounds (NTIOUArray arr) = getBounds arr
  getNumElements (NTIOUArray arr) = getNumElements arr
  newArray (a,b) e = NTIOUArray <$> newArray (a,b) (coerce e)
  newArray_ (a,b) = NTIOUArray <$> newArray_ (a,b)
  unsafeNewArray_ (a,b) = NTIOUArray <$> unsafeNewArray_ (a,b)
  unsafeRead (NTIOUArray arr) i = coerce <$> unsafeRead arr i
  unsafeWrite (NTIOUArray arr) i e = unsafeWrite arr i (coerce e)

Nun sollten Sie in der Lage sein zu verwenden NTIOUArrayund NTSTUArrayanstelle der üblichen IOUArrayund STUArrayfür beide eingebauten und Ihre newtype Elementtypen:

main = do
  x <- newArray (1,10) (MyInt 0) :: IO (NTIOUArray Int MyInt)
  y <- newArray (1,10) 0         :: IO (NTIOUArray Int Int)
  readArray x 5 >>= writeArray y 8 . coerce

Alle IArrayInstanzen können mithilfe der viaAbleitung automatisch generiert werden (was funktioniert, da der Elementtyp das letzte Argument für die IArrayEinschränkung ist):

deriving via MyBool instance IArray UArray MyBool
deriving via MyInt instance IArray UArray MyInt

oder Sie könnten die gleiche Technik wie oben mit einem NTIArrayNewtype verwenden.

Einige Beispielcodes:

{-# LANGUAGE DerivingVia, FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving,
    MultiParamTypeClasses, StandaloneDeriving, TypeFamilies, UndecidableInstances #-}

import Data.Coerce (coerce, Coercible)
import Data.Array.Base
import Data.Array.IO
import Control.Monad.ST (ST)

newtype MyBool = MyBool Bool deriving (Show)
newtype MyInt = MyInt Int deriving (Show)

-- newtype arrays
type family UType e where
  UType MyBool = Bool
  UType MyInt = Int
  UType a = a
newtype NTSTUArray s i e = NTSTUArray (STUArray s i (UType e))
newtype NTIOUArray i e = NTIOUArray (IOUArray i (UType e))

deriving via MyBool instance IArray UArray MyBool
deriving via MyInt instance IArray UArray MyInt

instance (MArray (STUArray s) (UType e) (ST s), Coercible e (UType e))
       => MArray (NTSTUArray s) e (ST s) where
  getBounds (NTSTUArray arr) = getBounds arr
  getNumElements (NTSTUArray arr) = getNumElements arr
  newArray (a,b) e = NTSTUArray <$> newArray (a,b) (coerce e)
  newArray_ (a,b) = NTSTUArray <$> newArray_ (a,b)
  unsafeNewArray_ (a,b) = NTSTUArray <$> unsafeNewArray_ (a,b)
  unsafeRead (NTSTUArray arr) i = coerce <$> unsafeRead arr i
  unsafeWrite (NTSTUArray arr) i e = unsafeWrite arr i (coerce e)

instance (MArray IOUArray (UType e) IO, Coercible e (UType e))
       => MArray NTIOUArray e IO where
  getBounds (NTIOUArray arr) = getBounds arr
  getNumElements (NTIOUArray arr) = getNumElements arr
  newArray (a,b) e = NTIOUArray <$> newArray (a,b) (coerce e)
  newArray_ (a,b) = NTIOUArray <$> newArray_ (a,b)
  unsafeNewArray_ (a,b) = NTIOUArray <$> unsafeNewArray_ (a,b)
  unsafeRead (NTIOUArray arr) i = coerce <$> unsafeRead arr i
  unsafeWrite (NTIOUArray arr) i e = unsafeWrite arr i (coerce e)

main = do
  x <- newArray (1,10) (MyInt 0) :: IO (NTIOUArray Int MyInt)
  y <- newArray (1,10) 0         :: IO (NTIOUArray Int Int)
  readArray x 5 >>= writeArray y 8 . coerce
  x' <- freeze x :: IO (UArray Int MyInt)
  y' <- freeze y :: IO (UArray Int Int)
  print $ (x' ! 5, y' ! 8)

foo :: ST s (NTSTUArray s Int MyInt)
foo = newArray (1,10) (MyInt 0)
KA Buhr
quelle
Könnten Sie bitte näher darauf eingehen Okay, you're kind of stuck here because some design choices in the array package have made it difficult.? Ich verstehe nicht, was beim Ableiten schief geht und was der folgende Fehler bedeutet. `Eine abgeleitete Instanz von 'MArray IOUArray MyInt IO' kann nicht erstellt werden (auch bei gerissenem GeneralizedNewtypeDeriving): Der Darstellungstyp kann nicht ausreichend reduziert werden. In der eigenständigen Ableitungsinstanz für 'MArray IOUArray MyInt IO'`
Arkady Rost