I'm working on a very simple toy API to improve my Haskell skills.
The relevant database table is called ingredients
and has some fields (id, name, category)
.
I'm now trying to get a selection query working that shows possible duplicates. It does this in a naive way. If two ingredients have the same name, it might be a duplicate. It is however allowed to have ingredients with the same name, it's just that they might be duplicates. It doesn't really matter if this restraint doesn't make sense since it's just a toy project.
Basically I would like to get the following query in esqueleto:
SELECT name FROM ingredients
GROUP BY name
HAVING count(*) > 1;
I succeeded in creating the following esqueleto code
getDuplicateIngredientsStmt :: SqlPersistT (LoggingT IO) [E.Value Text]
getDuplicateIngredientsStmt = E.select
$ E.from
$ \ingredients -> do
E.groupBy $ ingredients E.^. IngredientName
E.having $ (E.countRows :: SqlExpr (E.Value Int)) E.>. E.val 1
return $ ingredients E.^. IngredientName
This all works and compiles, great. But is this really as simple as it gets?
I'm mainly frustrated with me having to cast the countRows
to an SqlExpr
is this really necessary?
If I leave out the cast I get the following error (telling me I need the cast)
Ambiguous type variable ‘typ0’ arising from a use of ‘countRows’
prevents the constraint ‘(Num typ0)’ from being solved.
Probable fix: use a type annotation to specify what ‘typ0’ should be.
CodePudding user response:
Ah, you're already using the TypeApplications
extension. That provides an even easier approach.
E.having $ E.countRows E.>. E.val @Int 1
That sets the only free type variable in the type of E.val
to Int
, which does everything you need.
As for why it's necessary, it's because the types of E.val
and E.>.
introduce a PersistField
constraint, so 1
ends up being inferred to have the type (PersistField t, Num t) => t
. This prevents defaulting because the regular defaulting rules apply only when the constraints are in a minimal set that the compiler knows about. (The ExtendedDefaultRules
extension would cause it to default to Integer
because it knows what to do with the Num
portion of the constraint, but I don't think that's really what you're after.)
I'm just baffled that the error message you quoted doesn't mention both constraints. That would have made the lack of defaulting much more immediately obvious.