haskellari / postgresql-simple

Mid-level client library for accessing PostgreSQL from Haskell
Other
85 stars 43 forks source link

Non-lawful Eq Null instance? #78

Closed nomeata closed 11 months ago

nomeata commented 2 years ago

I found this code in https://github.com/haskellari/postgresql-simple/blob/master/src/Database/PostgreSQL/Simple/Types.hs#L53-L55:

instance Eq Null where
    _ == _ = False
    _ /= _ = False

but no rationale for this definition. Why was this definition done? (It has been there since “Initial commit”, so git blame does not give any insights.)

phadej commented 2 years ago
% psql
psql (10.18 (Ubuntu 10.18-0ubuntu0.18.04.1))
Type "help" for help.

phadej=# select 1 where null = null;
 ?column? 
----------
(0 rows)

phadej=# select 1 where null != null;
 ?column? 
----------
(0 rows)
nomeata commented 2 years ago

Hmm, I see. And the Haskell == has to match the SQL = here? Is this a property that is guaranteed in general?

phadej commented 2 years ago

And the Haskell == has to match the SQL = here?

I don't know. I guess it's a bit like nan == (nan :: Double) = False, even people argue for and against.

Is this a property that is guaranteed in general?

For other types? I'd consider it a potential bug if type's PostgreSQL equality and its canonical representation in Haskell disagree on equality, but I don't think we test that. (the types are not that complicated, but maybe time, json, ...)

nomeata commented 2 years ago

Hmm, but as it stands it is an unlawful instance, and may cause surprising effects when passed to code that relies on (/=) being the negation of (==).

phadej commented 2 years ago

Hmm, but as it stands it is an unlawful instance,

So is Doubles instance not reflexive.

phadej commented 2 years ago

If this is related to your single method Eq instance proposal, the Eq Null is not an issue. No test fails if I just derive Eq Null, so I think Bryan just felt "smart" when writing the initial version of a library.

nomeata commented 2 years ago

Thanks for testing that :-)

phadej commented 2 years ago

For reference, that's a commit which added it to mysql-simple: https://github.com/paul-rouse/mysql-simple/commit/6260c7dbecc2bf4ed3673a087f566f9bfc135a32

Add basic documentation.

But no hint why Eq Null is the way it is.

AntC2 commented 2 years ago

But no hint why Eq Null is the way it is.

SQL Null is the way it is because SQL has 'three-value logic'. (I tend to call it "three-and-a-half value logic"; or not any sort of logic).

Note that unlike haskell's undefined; SQL's Null is a value you can assign/store in a variable/write out to a database/etc. It's distinct from zero or from a zero-length string. You're supposed to test for it using ... IS [NOT] NULL, not using equality. (SQL doesn't have anything like a Maybe type.)

It's just so darn convenient (= quick-and-dirty) that testing any non-Null value = Null returns False (in effect, see below).

@nomeata And the Haskell == has to match the SQL = here?

I think not. I can see no reason why you'd have instance Eq Null.

Is this a property that is guaranteed in general?

The general rule in SQL is that Null = <anything> returns Null. But if that result bubbles up to SELECT ... WHERE ... statement level, the DB engine has to decide whether to include the row; then a Null result means don't select the row; IOW treat the result as if False.

I see that PostreSQL repo says:

-- | A placeholder for the SQL @NULL@ value.

Must be written by somebody who doesn't understand SQL: Null is not a "value". It's a 'marker' for the absence of a value (in a field whose type contains 'proper' values like INT, CHAR, ...). Hence its behaviour wrt equality testing. I see no reason that repo should even define data Null = ..., let alone give an Eq instance. SQL has no type Null; fields of a genuine type can be marked NULLABLE.

phadej commented 2 years ago

Thank you, I know what three value logic is. That doesn't explain why Eq Null instance was added.

phadej commented 11 months ago

Instance is removed in #120 (postgresql-simple-0.7.0.0)