3

I'm trying to deriving Eq from data type with a function as a field but doesn't work as expected.

I also try to write te instance but still doesn't work

data Conf = Conf {
    rule :: ([Char] -> Char),
    start :: Int,
    numLines :: Double,
    window :: Int,
    move :: Int,
    actualLine :: Int,
    lastLine :: String
} deriving (Eq)

It's a project that consist in print graphicaly the wolfram pyramids, for instance, the rules are for example:

rule30 :: [Char] -> Char
rule30 "***" = ' '
rule30 "** " = ' '
rule30 "* *" = ' '
rule30 "*  " = '*'
rule30 " **" = '*'
rule30 " * " = '*'
rule30 "  *" = '*'
rule30 "   " = ' '
rule30 _     = ' '

There are many rules to follow, it's for that reason that I want to save the "function pointer" directly in Conf data type.

So, why I need the deriving(Eq)? I need it because in the main I check if is Nothing (error handling check, for example if the user puts a bad rule...)

Error Msg:

src/Wolf.hs:24:13: error:
• No instance for (Eq ([Char] -> Char))
    arising from the first field of ‘Conf’ (type ‘[Char] -> Char’)
    (maybe you haven't applied a function to enough arguments?)
  Possible fix:
    use a standalone 'deriving instance' declaration,
      so you can specify the instance context yourself
• When deriving the instance for (Eq Conf)
   |
24 | } deriving (Eq)
   |             ^^

What am I missing?x

2 Answers2

5

What makes you think that this should be possible? If your type contains a function field, then comparing values of your type for equality is at least as difficult as comparing functions for equality. But to check that two functions are equal (in Haskell, the only sensible meaning is extensional equality), you'd need to check that they agree on all possible inputs. That's an utterly infeasible thing to do, even for simple Int inputs but certainly if the arguments have type [Char].

So, why I need the deriving(Eq)? I need it because in the main I check if is Nothing

You totally don't need Eq for that! Testing whether a Maybe value is Nothing by using == is ineffective, even on those types where it is possible. You should instead use either pattern matching

main = do
   ...
   let myConfq = ... :: Maybe Conf
   case myConfq of
     Nothing -> error "Meh, couldn't have conf"
     Just conf -> ...

...or use higher level combinators, perhaps based on Maybes Applicative or Traversable instances

import Data.Traversable

main = do
   ...
   let myConfq = ... :: Maybe Conf
   traverse ... myConfq
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • "Testing whether a Maybe value is Nothing by using == is a horribly inefficient way, even on those types where it is possible" While I agree that you *shouldn't* check it this way, I don't see that this is a valid reason. If `a` has an `Eq` instance then using `== Nothing` on a `Maybe a` value will simply use pattern matching in the same way that you would do it manually, so I don't see how it's any less efficient. It's wrong imo mainly because it forces you to have an `Eq` instance from `a` when this is in fact completely unnecessary - as you already explain. – Robin Zigmond Mar 10 '22 at 18:09
  • 2
    @RobinZigmond fair enough, yes, `(== Nothing)` by itself is fast enough. The problem is only that this doesn't do anything to give you access to the contained value in the `Just` case, which will more likely than not also be required. — What _is_ really horribly inefficient is the similar checking whether a list is empty with `length l == 0`. Again, changing this to `l == []` would avoid the unnecessary traversing of the whole thing, but it would still probably not be the way to go. – leftaroundabout Mar 10 '22 at 18:18
3

I am thinking about ideas that would allow annotating fields of a datatype that would allow what you want: Via fields: finer granularity in deriving

The idea is to define a newtype where comparisons always succeed:

newtype Ignore a = Ignore a

instance Eq (Ignore a) where
  _ == _ = True

instance Ord (Ignore a) where
  compare _ _ = EQ

and then annotating only the function field; then when we derive instances of the datatype the instances that manipulate the field (==) @([Char] -> Char) are actually performed via the newtype (==) @(via Ignore):

data Conf = Conf
  { rule  :: [Char] -> Char
         via Ignore ([Char] -> Char)
  , start :: Int
  , ..
  }
  deriving
  stock (Eq, Ord)
Iceland_jack
  • 6,848
  • 7
  • 37
  • 46
  • Although Via Fields is a nice thing to mention, I'd strongly advice against using this for hacking together an `Eq` instance. Two values that compare equal should be, if not structurally identical, then at least behave the same for anything that can be observed through the type's public interface. I doubt that this is the case here. – leftaroundabout Mar 10 '22 at 18:33
  • That's right the answer is for the technique and not the use case which your answer addresses. The technique is more common for other classes that aren't as structural but even for equality, hashing and serialization there are nonhack applications. It is not uncommon that a type includes an ID/fingerprint or where other fields contain metadata (like tags/types/derivative/annotations or length and offset of some primitive array) that don't factor into the equality of the type. – Iceland_jack Mar 10 '22 at 20:04