Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates via “ix”, “at” do not seem to update secondary indices #15

Closed
ion1 opened this issue Jun 11, 2013 · 7 comments
Closed
Assignees
Labels

Comments

@ion1
Copy link
Collaborator

ion1 commented Jun 11, 2013

tables 0.3.1

{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}

module Foo where

import Control.Applicative
import Control.Lens
import Data.Table

data Foo = Foo { _k :: String, _v :: String }
  deriving (Eq, Ord, Show, Read)

makeLenses ''Foo

instance Tabular Foo where
  type PKT Foo = String
  data Key k Foo a where
    FooK      :: Key Primary Foo String
    FooLength :: Key Supplemental Foo Int
  data Tab Foo i = FooTab (i Primary String)
                          (i Supplemental Int)

  fetch FooK = _k
  fetch FooLength = length . _v

  primary = FooK
  primarily FooK r = r

  mkTab f               = FooTab <$> f FooK   <*> f FooLength
  forTab (FooTab a b) f = FooTab <$> f FooK a <*> f FooLength b
  ixTab (FooTab a _) FooK      = a
  ixTab (FooTab _ b) FooLength = b

tableA, tableB, tableC, tableD, tableE, tableF :: Table Foo

tableA = fromList [Foo "a" "A", Foo "b" "hello"]
-- fromList [Foo {_k = "a", _v = "A"},Foo {_k = "b", _v = "hello"}]

tableB = tableA & ix "a" . v %~ take 8 . cycle
                & ix "b" . v %~ take 8 . cycle . take 2
-- fromList [Foo {_k = "a", _v = "AAAAAAAA"},Foo {_k = "b", _v = "hehehehe"}]

tableC = tableB ^. with FooLength (<=) 3
-- fromList [Foo {_k = "a", _v = "A"}]

tableD = tableB ^. with FooLength (>)  3
-- fromList [Foo {_k = "b", _v = "hello"}]

tableE = tableB ^. with FooLength (>=) 0
-- fromList [Foo {_k = "a", _v = "A"},Foo {_k = "b", _v = "hello"}]

tableF = tableB ^. with FooK (/=) ""
-- fromList [Foo {_k = "a", _v = "AAAAAAAA"},Foo {_k = "b", _v = "hehehehe"}]
@Taneb
Copy link
Collaborator

Taneb commented Jun 11, 2013

I don't think this qualified as a bug. Consider how it would work in a traditional database, you would either have one field for the text and one field for the length and update manually, or you would just have a single field and calculate the length. You can do "with (\Foo k -> length k) (<=) 3" and it would work fine.

@ion1
Copy link
Collaborator Author

ion1 commented Jun 11, 2013

{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}

module Foo where

import Control.Applicative
import Control.Lens
import Data.Table

data Foo = Foo { _k :: String, _v :: String }
  deriving (Eq, Ord, Show, Read)

makeLenses ''Foo

instance Tabular Foo where
  type PKT Foo = String
  data Key k Foo a where
    FooK :: Key Primary Foo String
    FooV :: Key Supplemental Foo String
  data Tab Foo i = FooTab (i Primary String)
                          (i Supplemental String)

  fetch FooK = _k
  fetch FooV = _v

  primary = FooK
  primarily FooK r = r

  mkTab f               = FooTab <$> f FooK   <*> f FooV
  forTab (FooTab a b) f = FooTab <$> f FooK a <*> f FooV b
  ixTab (FooTab a _) FooK = a
  ixTab (FooTab _ b) FooV = b

tableA, tableB, tableC, tableD, tableE, tableF, tableG, tableH :: Table Foo

tableA = fromList [Foo "a" "A", Foo "b" "hello"]
-- fromList [Foo {_k = "a", _v = "A"},Foo {_k = "b", _v = "hello"}]

tableB = tableA & ix "a" . v %~ take 8 . cycle
                & ix "b" . v %~ take 8 . cycle . take 2
-- fromList [Foo {_k = "a", _v = "AAAAAAAA"},Foo {_k = "b", _v = "hehehehe"}]

tableC = tableB ^. with FooV (/=) ""
-- fromList [Foo {_k = "a", _v = "A"},Foo {_k = "b", _v = "hello"}]

tableD = tableB ^. with _v   (/=) ""
-- fromList [Foo {_k = "a", _v = "AAAAAAAA"},Foo {_k = "b", _v = "hehehehe"}]

tableE = tableB ^. with FooV (==) "A"
-- fromList [Foo {_k = "a", _v = "A"}]

tableF = tableB ^. with _v   (==) "A"
-- fromList []

tableG = tableB ^. with FooV (==) "AAAAAAAA"
-- fromList []

tableH = tableB ^. with _v   (==) "AAAAAAAA"
-- fromList [Foo {_k = "a", _v = "AAAAAAAA"}]

@ekmett
Copy link
Owner

ekmett commented Jun 11, 2013

That looks like a bug to me!

fisx added a commit to zerobuzz/tables that referenced this issue Sep 2, 2015
@fisx fisx mentioned this issue Sep 2, 2015
@Taneb
Copy link
Collaborator

Taneb commented Nov 23, 2015

The issue is that setting with "primaryMap" doesn't update any of the copies of the table outside the map being modified, which breaks an invariant on Table.

Luckily primaryMap is not exported, and is only used to set in the definition of ix and at. Because in each of these we know that only one record is being modified, it would be more efficient, I believe, to readjust the table in ix and at than changing primaryMap, where concievably the entire Map could be changed.

I am working on a patch fixing this.

@fisx
Copy link
Collaborator

fisx commented Nov 23, 2015

Cool, thanks! I haven't had time for this, but I'd love to see this un-deprecated on hackage.

@Taneb
Copy link
Collaborator

Taneb commented Nov 23, 2015

I've worked on this some more, and I have come to an unfortunate conclusion: Table t has no valid Ix or At instance that preserves the invariant whose breakage lead to this issue.

Consider data Foo = Foo {_p :: Int, _c :: Int} with lenses and a Tabular instance with primary key of p, and a candidate key of c, and a t = fromList [Foo 1 1, Foo 2 2]

t & ix 1 . c %~ (+1) & ix 1 . c %~ (+1) would result in fromList [Foo 1 3], deleting Foo 2 2 because the intermediate state would have a collision with that.

However, t & ix 1 . c %~ (+1).(+1) would not have this intermediate state, and so would result in fromList [Foo 1 3, Foo 2 2]

This breaks the Traversal laws, and there is a similar story for At.

This prevents us with three options, as far as I can tell:

  • Leave the behaviour as it is, which is a valid Traversal but leads to unexpected, buggy behaviour
  • Implement the illegal instances, with a big fat warning on them.
  • Remove the instances entirely.

None of these options are particularly satisfying. Anyone have any better ideas?

@ekmett
Copy link
Owner

ekmett commented Nov 23, 2015

The instances in this package are actually expected to violate the laws around fusion.

The very reason this package was put together was to explore what would happen if we built a library that just "operationally" used lens, but ignored its laws. How bad would it be?

The illegal instances you mention are the intended behavior, not the current "quasi-legal" ones, which seem to have arisen by accident.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants