Bagaimana saya bisa membungkus ulang instance kelas tipe

Saya ingin mewakili konsep batas atas dalam kode saya, jadi saya membuat kesatuan yang terdiskriminasi:

data UpperBound a = UpperBound a | DoNotCare deriving (Eq, Read, Show)

Saya kemudian secara manual memperoleh beberapa contoh kelas tipe yang berguna (untuk tujuan pembelajaran):

instance Functor UpperBound where
    fmap _ DoNotCare = DoNotCare
    fmap f (UpperBound x) = UpperBound $ f x

instance Applicative UpperBound where
    pure = UpperBound
    DoNotCare <*> _ = DoNotCare
    _ <*> DoNotCare = DoNotCare
    (UpperBound f) <*> (UpperBound x) = UpperBound $ f x

instance Foldable UpperBound where
    foldr _ s DoNotCare = s
    foldr f s (UpperBound x) = f x s

instance Traversable UpperBound where
    traverse _ DoNotCare = pure DoNotCare
    traverse f (UpperBound x) = fmap UpperBound $ f x

instance Alternative UpperBound where
    empty = DoNotCare
    DoNotCare <|> x = x
    x <|> DoNotCare = x
    x <|> _ = x

instance Monad UpperBound where
    return = pure
    DoNotCare >>= _ = DoNotCare
    (UpperBound x) >>= f = f x

instance MonadPlus UpperBound where
    mzero = empty
    mplus = (<|>)

dan satu fungsi utilitas:

isWithinBound :: Ord a => a -> UpperBound a -> Bool
isWithinBound _ DoNotCare = True
isWithinBound x (UpperBound b) = x <= b

Instance kelas tipe terlihat hampir sama dengan yang untuk Maybe (Just x -> UpperBound x, Nothing -> DoNotCare) jadi sepertinya saya jelas-jelas mengalami duplikasi yang tidak perlu di sini.

Bagaimana saya bisa 'membungkus' Maybe dan mengarahkan implementasi instance kelas tipe ke sana dan tetap mengekspos fungsi isWithinBound?


person rexcfnghk    schedule 22.02.2019    source sumber
comment
Tidak sepenuhnya yakin saya memahami pertanyaannya - tetapi tidak bisakah Anda menggunakan newtype saja? Atau bahkan jenis sinonim jika instance Anda benar-benar identik (seperti yang terlihat).   -  person Robin Zigmond    schedule 22.02.2019
comment
Mengenai isWithinBound, penulisan ulang itu mudah sehingga diperlukan Maybe a alih-alih versi khusus Anda.   -  person Robin Zigmond    schedule 22.02.2019
comment
@RobinZigmond Maksud saya, saya ingin tetap mengekspos kasus sebagai UpperBound/DoNotCare tetapi secara internal meneruskan deklarasi instance ke implementasi Maybe. Apakah itu mungkin?   -  person rexcfnghk    schedule 22.02.2019
comment
Instance kelas tipe terlihat hampir sama dengan instance untuk Maybe dari mana sebagian besarnya berasal?   -  person n. 1.8e9-where's-my-share m.    schedule 22.02.2019
comment
@n.m. Anda hampir dapat melakukan penggantian regex global dari UpperBound menjadi Just dan DoNotCare menjadi None di semua implementasi instance kelas tipe dan kode akan tetap dikompilasi untuk tipe Maybe.   -  person rexcfnghk    schedule 22.02.2019
comment
Contoh Alternative itu tampak samar bagi saya. Ini seharusnya menjadi monoid untuk setiap tipe yang terkandung (dengan empty sebagai unit dan <|> sebagai operasi) -- dan khususnya seharusnya memiliki empty <|> x = x <|> empty = x, sedangkan milik Anda tidak.   -  person Daniel Wagner    schedule 22.02.2019
comment
@DanielWagner Anda benar. Saya telah mengubah kode saya untuk memperbaiki kebingungan itu.   -  person rexcfnghk    schedule 03.03.2019


Jawaban (1)


Cara termudah adalah dengan menggunakan ekstensi newtype dan GeneralizedNewtypeDeriving, seperti ini:

{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveTraversable #-}
module UpperBound (UpperBound, isWithinBound) where

import Control.Monad
import Control.Applicative

newtype UpperBound a = UpperBound { unUpperBound :: Maybe a }
  deriving (Functor, Applicative, Foldable, Traversable, Alternative, Monad, MonadPlus)

isWithinBound :: Ord a => a -> UpperBound a -> Bool
isWithinBound x = maybe True ((<=) x) . unUpperBound

Jadi semua instance yang Anda butuhkan secara otomatis dialihkan ke Maybe (selain Traversable yang diturunkan secara otomatis menggunakan ekstensi DeriveTraversable).

person Karol Samborski    schedule 22.02.2019
comment
Saya tidak pernah menyadari GeneralizedNewtypeDeriving tidak berfungsi untuk Traversable (Saya tidak pernah menggunakannya selain untuk Functor, Applicative dan Monad). Saya ingin tahu mengapa ini bisa terjadi. - person Robin Zigmond; 22.02.2019
comment
@RobinZogmond Jika tipe baru U x = U (T x) dan Traversable T, maka traverse mengembalikan a f (T x) di mana Anda memerlukan f (U x). GHC menginginkan adanya pemaksaan tanpa biaya di antara keduanya, namun hal tersebut tidak terjadi. Anda perlu memetakan U, dan GHC tidak akan melakukannya dengan sendirinya. Sekarang, sejak Functor f, secara moral f harus selalu berupa tipe peran f representasional, dan harus ada paksaan, tetapi 1) Traversable tidak memiliki batasan tersebut 2) Ada beberapa “rusak ” tetapi fungsi yang berguna melanggar aturan ini 3) GND mungkin tidak dapat menggunakan informasi tersebut meskipun informasi tersebut diberikan dan 4) kendala tersebut merupakan ekstensi GHC yang sangat baru. - person HTNW; 23.02.2019