0

In my application, I'd like to index sets of objects in a type-safe way using a structure similar to a relational database index. For example, I might want to index a set of User objects based on age and name:

import Data.Map as M
import Data.Set as S

type AgeNameIndex = M.Map Int (M.Map String (S.Set User))

Furthermore, I'd like to do operations like union and difference on indexes efficiently, e.g.:

let a = M.singleton 42 $ M.singleton "Bob" $ S.singleton $ User { ... }
    b = M.singleton 42 $ M.singleton "Tim" $ S.singleton $ User { ... }
    c = union a b -- contains both Bob and Tim

I've tried to model this as follows:

module Concelo.Index
  ( index
  , union
  , subtract
  , lastValue
  , subIndex ) where

import Prelude (($), (>>>), flip, Unit, unit, class Ord)
import Control.Monad ((>>=))
import Data.Map as M
import Data.Set as S
import Data.Maybe (Maybe(Nothing, Just), fromMaybe)
import Data.Tuple (Tuple(Tuple))
import Data.Foldable (foldl)
import Data.Monoid (mempty)

class Index index key value subindex where
  isEmpty :: index -> Boolean
  union :: index -> index -> index
  subtract :: index -> index -> index
  lastValue :: index -> Maybe value
  subIndex :: key -> index -> subindex

instance mapIndex :: (Index subindex subkey value subsubindex) =>
                     Index (M.Map key subindex) key value subindex where
  isEmpty = M.isEmpty

  union small large =
    foldl (m (Tuple k v) -> M.alter (combine v) k m) large (M.toList small)
    where
      combine v = case _ of
        Just v' -> Just $ union v v'
        Nothing -> Just v

  subtract small large =
    foldl (m (Tuple k v) -> M.alter (minus v) k m) large (M.toList small)
    where
      minus v = (_ >>= v' ->
                  let subindex = subtract v v' in
                  if isEmpty subindex then Nothing else Just subindex)

  lastValue m = M.findMax m >>= (_.value >>> lastValue)

  subIndex k m = fromMaybe mempty $ M.lookup k m

instance setIndex :: (Ord value) => Index (S.Set value) Unit value Unit where
  isEmpty = S.isEmpty

  union = S.union

  subtract = flip S.difference

  lastValue s = Nothing -- todo: S.findMax

  subIndex _ _ = unit

index f = foldl (acc v -> union (f v) acc) mempty

However, the Purescript compiler doesn't like that:

Compiling Concelo.Index
Error found:
in module Concelo.Index
at /home/dicej/p/pssync/src/Concelo/Index.purs line 24, column 1 - line 44, column 49

  No type class instance was found for

    Concelo.Index.Index subindex0
                        t1       
                        t2       
                        t3       

  The instance head contains unknown type variables. Consider adding a type annotation.

in value declaration mapIndex

where subindex0 is a rigid type variable
      t1 is an unknown type
      t2 is an unknown type
      t3 is an unknown type

See https://github.com/purescript/purescript/wiki/Error-Code-NoInstanceFound for more information,
or to contribute content related to this error.

My understanding of this message is that I haven't properly stated that map values in the mapIndex instance are themselves Index instances, but I don't know how to fix that. Where might I add a type annotation to make this compile? Or am I even on the right track given what I'm trying to do?

Joel Dice
  • 121
  • 1
  • 6

1 Answers1

1

This is almost certainly because PureScript currently lacks functional dependencies (or type families) which makes this kind of information un-inferrable. There's a writeup of the issue here: https://github.com/purescript/purescript/issues/1580 - it is something we want to support.

There was a discussion about a case very similar to this today as it happens: https://github.com/purescript/purescript/issues/2235

Essentially, the problem here is that the functions of the class do not use all of the type variables, which means there's no way to propagate the information to the constraint for looking up a suitable instance.

I don't really have a suggestion for how to do what you're after here with things as they are, aside from avoiding the class and implementing it with specific types in mind.

gb.
  • 4,629
  • 1
  • 20
  • 19
  • Thanks, Gary. I came across #1580 independently and wondered if it might have something to do with my issue. #1280 also seemed relevant. Meanwhile, I'll see if I can manage without a class. – Joel Dice Jul 20 '16 at 03:44
  • Any thoughts on how my problem might be solved now that PureScript has functional dependencies? – Joel Dice Dec 16 '16 at 20:52