为包含不能有 Eq 或 Show 的字段的 ADT 派生 Eq 和 Show

che*_*ter 3 haskell deriving derivingvia

我希望能够为包含多个字段的 ADT派生Eq和派生Show。其中之一是功能字段。这样做时Show,我希望它显示一些虚假的东西,例如"<function>"; 这样做时Eq,我希望它忽略该字段。我怎样才能最好地做到这一点,而无需为Showand手写一个完整的实例Eq

我不想将函数字段包装在 a 中并为此newtype编写我自己的字段- 那样使用太麻烦了。EqShow

lef*_*out 8

获得正确EqShow实例的一种方法是,不要对该函数字段进行硬编码,而是将其设为类型参数并提供一个仅“擦除”该字段的函数。也就是说,如果你有

data Foo = Foo
  { fooI :: Int
  , fooF :: Int -> Int }
Run Code Online (Sandbox Code Playgroud)

你把它改成

data Foo' f = Foo
  { _fooI :: Int
  , _fooF :: f }
 deriving (Eq, Show)
type Foo = Foo' (Int -> Int)

eraseFn :: Foo -> Foo' ()
eraseFn foo = foo{ fooF = () }
Run Code Online (Sandbox Code Playgroud)

然后,Foo仍然不会Eq- 或Show能够(毕竟它不应该是),但是要使Foo值可显示,您可以将其包装在eraseFn.


Jon*_*rdy 6

通常,我在这种情况下做的正是你说你想要做的,即在包装的功能newtype,并提供了一个Show为:

data T1
  { f :: X -> Y
  , xs :: [String]
  , ys :: [Bool]
  }
Run Code Online (Sandbox Code Playgroud)
data T2
  { f :: OpaqueFunction X Y
  , xs :: [String]
  , ys :: [Bool]
  }
  deriving (Show)

newtype OpaqueFunction a b = OpaqueFunction (a -> b)

instance Show (OpaqueFunction a b) where
  show = const "<function>"
Run Code Online (Sandbox Code Playgroud)

如果您不想这样做,您可以将函数设为类型参数,并在Show输入类型时将其替换掉:

data T3' a
  { f :: a
  , xs :: [String]
  , ys :: [Bool]
  }
  deriving (Functor, Show)

newtype T3 = T3 (T3' (X -> Y))

data Opaque = Opaque

instance Show Opaque where
  show = const "..."

instance Show T3 where
  show (T3 t) = show (Opaque <$ t)
Run Code Online (Sandbox Code Playgroud)

或者我将重构我的数据类型以Show仅派生我希望Show默认使用的部分,并覆盖其他部分:

data T4 = T4
  { f :: X -> Y
  , xys :: T4'     -- Move the other fields into another type.
  }

instance Show T4 where
  show (T4 f xys) = "T4 <function> " <> show xys

data T4' = T4'
  { xs :: [String]
  , ys :: [Bool]
  }
  deriving (Show)  -- Derive ‘Show’ for the showable fields.
Run Code Online (Sandbox Code Playgroud)

或者,如果我的类型很小,我将使用 anewtype而不是data,并Show通过以下方式派生OpaqueFunction

{-# LANGUAGE DerivingVia #-}

newtype T5 = T5 (X -> Y, [String], [Bool])
  deriving (Show) via (OpaqueFunction X Y, [String], [Bool])
Run Code Online (Sandbox Code Playgroud)

如果您关心保留字段名称/记录访问器,您可以使用该iso-deriving包为data使用镜头的类型执行此操作。

至于Eq(or Ord),让一个实例等同于可以以某种方式明显区分的值并不是一个好主意,因为某些代码会将它们视为相同而其他代码则不会,现在您不得不关心稳定性:在某些情况下,我a == b应该选择a还是b?这就是为什么可替代性是一个定律Eqforall x y f. (x == y) ==> (f x == f y)iff是一个“公共”函数,它支持xand类型的不变量y(尽管浮点数也违反了这一点)。更好的选择是类似T4上面的内容,仅对可以满足法律的类型的部分具有相等性,或者在使用站点显式使用比较模某个函数,例如,comparing someField.