3

The problem I'm facing is pretty simple: Basically I'm trying to calculate the product of an Int and a Double. In plain Haskell I would just run

product = (fromIntegral int_val) * double_val

However I can't figure out how to do it in esqueleto. I'm having a table B that has a column "amount" of type Int and a table C that has a column "price" of type Double. When trying to extract both and calculate the product, like this

(b ^. BAmount) *. (c ^. CPrice) 

I'm getting a type error (as expected):

Couldn't match type ‘Double’ with ‘Int’
Expected type: EntityField Drink Int
  Actual type: EntityField Drink Double

I couldn't find anything in the docs that helped me and I actually have no idea how to go on. (For more code see the full example below).

Possible Solution: I could of course just store the price as an Int, but I'm interested if this can be done with esqueleto.

Full example:

Database:

Table A: Id|Name

Table B: Id|AId|BId|Amount where Amount is an Int and AId and BId are references to Table A and B.

Table C: Id|Name|Price , here is Price a Double

The query I've written is as follows:

result <- liftIO $ runDb $ select $
            from $ \(a, b, c) -> do
              where_ (a ^. AId ==. b ^. BAId)
              where_ (b ^. BCId ==. c ^. CId)
              let product = (b ^. BAmount) *. (c ^. CPrice)
              let total = sum_ product :: SqlExpr (Value (Maybe Double))
              groupBy $ a ^. AName
              return (a ^. AName)

EDIT:

I've tried using fmap and fromIntegral like this:

let product = fmap fromIntegral (b ^. BAmount) *. (c ^. CPrice)

which results in two errors: No instance for (Functor SqlExpr) and No instance for (Num (Value Double))

As suggested in the comments (by @Thomas M. DuBuisson), I tried:

let product = fmap (fmap fromIntegral) (b ^. BAmount) *. (c ^. CPrice)

which solves the second problem, but I still get No instance for (Functor SqlExpr).

EDIT 2:

I've asked about this on the Yesod mailing list. The discussion can be found here.

Joel Hermanns
  • 355
  • 1
  • 4
  • 11
  • 1
    Never having heard of the library, but looking at the types, I think `EntityField` if your data type, right? So if you make it a functor then just `(fmap (fmap fromIntegral))` – Thomas M. DuBuisson Jul 02 '15 at 15:58
  • [esqueleto](https://hackage.haskell.org/package/esqueleto) builds on top of [persistent](https://hackage.haskell.org/package/persistent) where [`EntityField`](https://hackage.haskell.org/package/persistent-2.2/docs/Database-Persist-Class.html#t:EntityField) comes from. I tried using `fmap` already. I've tried your suggestion and I get (as earlier when using `fmap`): `No instance for (Functor SqlExpr) ...`. `[SqlExpr](http://hackage.haskell.org/package/esqueleto-2.2.7/docs/Database-Esqueleto.html#t:SqlExpr) is GADT defined in esqueleto and I don't know how to write that instance. – Joel Hermanns Jul 02 '15 at 16:20
  • What is the code you tried with? – Thomas M. DuBuisson Jul 02 '15 at 19:47
  • Sorry, added the code to the post (should've done that before) :) – Joel Hermanns Jul 03 '15 at 06:13

2 Answers2

1

I am not an expert, but you might try to apply _floor to your integer parameter:

floor_ :: (..., PersistField a, Num a, PersistField b, Num b)
       => expr (Value a) -> expr (Value b)

This might be able to turn your integer into a double, since a and b can be different. Usually you use it to turn doubles into integers, but it might also work the other way around.

Some similar Esqueleto functions might work as well.

And yes, this is a hack. I hope some expert can suggest a better solution.

chi
  • 111,837
  • 3
  • 133
  • 218
  • 1
    Awesome, that works! Although `floor_` is not supported by sqlite, which I use for development, similar functions like `round_` work pretty well. Looks like I just missed these functions when checking the docs. Thanks! However, I'm also interested if there is a better solution. – Joel Hermanns Jul 03 '15 at 06:15
0

As mentioned in the discussion here there is now (since esqueleto version 2.2.9) castNum for this purpose:

castNum :: (Num a, Num b) => expr (Value a) -> expr (Value b)
Joel Hermanns
  • 355
  • 1
  • 4
  • 11