packages feed

cpython-3.5.1: lib/CPython/Simple/Instances.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module CPython.Simple.Instances where

import Control.Exception (Exception(..), throwIO)
import Control.Monad ((<=<))
import Data.Text (Text)
import qualified Data.Text as T
import Data.Typeable

import qualified CPython.Constants as Py
import qualified CPython.Protocols.Object as Py
import qualified CPython.Types as Py
import qualified CPython.Types.Tuple as Py (fromTuple)

-- TODO: ToPy/FromPy for Bool will require some library changes (e.g. adding fromBool)

class ToPy a where
  toPy :: a -> IO Py.SomeObject

class FromPy a where
  fromPy :: Py.SomeObject -> IO a

data PyCastException = PyCastException String
  deriving (Show)

instance Exception PyCastException where
  displayException (PyCastException typename) =
    "FromPy could not cast to " ++ typename

easyToPy
  :: Py.Object c
  => (a -> IO c) -- ^ python to- conversion, e.g. Py.toFloat
  -> a           -- ^ haskell type being converted
  -> IO Py.SomeObject
easyToPy f = fmap Py.toObject . f

easyFromPy
  :: (Py.Concrete b, Typeable c)
  => (b -> IO c)   -- ^ python from- conversion, e.g. Py.fromFloat
  -> Proxy c       -- ^ proxy for the type being converted to
  -> Py.SomeObject -- ^ python object to cast from
  -> IO c
easyFromPy conversion typename obj = do
  casted <- Py.cast obj
  case casted of
    Nothing -> throwIO $ PyCastException (show $ typeRep typename)
    Just x -> conversion x

instance ToPy Integer where
  toPy = easyToPy Py.toInteger

instance FromPy Integer where
  fromPy = easyFromPy Py.fromInteger Proxy

instance ToPy Double where
  toPy = easyToPy Py.toFloat

instance FromPy Double where
  fromPy = easyFromPy Py.fromFloat Proxy

instance ToPy Text where
  toPy = easyToPy Py.toUnicode

instance FromPy Text where
  fromPy = easyFromPy Py.fromUnicode Proxy

instance ToPy Char where
  toPy = easyToPy Py.toUnicode . T.singleton

instance FromPy Char where
  fromPy c = T.head <$> easyFromPy Py.fromUnicode Proxy c

instance ToPy String where
  toPy = easyToPy Py.toUnicode . T.pack

instance FromPy String where
  fromPy s = T.unpack <$> easyFromPy Py.fromUnicode Proxy s

instance (FromPy a, FromPy b) => FromPy (a, b) where
  fromPy val = do
    [pyA, pyB] <- easyFromPy Py.fromTuple Proxy val
    a <- fromPy pyA
    b <- fromPy pyB
    pure (a, b)

instance (FromPy a, FromPy b, FromPy c) => FromPy (a, b, c) where
  fromPy val = do
    [pyA, pyB, pyC] <- easyFromPy Py.fromTuple Proxy val
    a <- fromPy pyA
    b <- fromPy pyB
    c <- fromPy pyC
    pure (a, b, c)

instance FromPy a => FromPy (Maybe a) where
  fromPy val = do
    isNone <- Py.isNone val
    if isNone
      then pure Nothing
      else Just <$> fromPy val

instance ToPy a => ToPy (Maybe a) where
  toPy Nothing = Py.none
  toPy (Just a) = toPy a

instance FromPy a => FromPy [a] where
  fromPy val = do
    list <- easyFromPy Py.fromList Proxy val
    mapM fromPy list

instance ToPy a => ToPy [a] where
  toPy val = do
    list <- mapM toPy val
    Py.toObject <$> Py.toList list

instance FromPy () where
  fromPy _ = pure ()