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

feat(bindings/haskell): enhance original OpMonad to support custom IO monad #2789

Merged
merged 4 commits into from
Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/bindings_haskell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,4 @@ jobs:
working-directory: "bindings/haskell"
run: |
cargo build
LIBRARY_PATH=../../target/debug cabal build
LD_LIBRARY_PATH=../../target/debug cabal test
LIBRARY_PATH=../../target/debug LD_LIBRARY_PATH=../../target/debug cabal test
18 changes: 0 additions & 18 deletions bindings/haskell/cabal.project.local

This file was deleted.

89 changes: 72 additions & 17 deletions bindings/haskell/haskell-src/OpenDAL.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
{-# LANGUAGE FlexibleInstances #-}

-- |
-- Module : OpenDAL
Expand All @@ -27,17 +26,28 @@
--
-- This module provides Haskell bindings for OpenDAL.
module OpenDAL
( OperatorConfig (..),
( -- * Operator APIs

-- ** Types
OperatorConfig (..),
Operator,
Lister,
OpenDALError (..),
ErrorCode (..),
EntryMode (..),
Metadata (..),
OpMonad,
OperatorT (..),
MonadOperation (..),

-- ** Functions
runOp,
newOperator,

-- * Lister APIs
nextLister,

-- * Operator Raw APIs
-- $raw-operations
readOpRaw,
writeOpRaw,
isExistOpRaw,
Expand All @@ -48,13 +58,13 @@ module OpenDAL
statOpRaw,
listOpRaw,
scanOpRaw,
nextLister,
)
where

import Colog (LogAction, Message, Msg (Msg), (<&))
import Control.Monad.Except (ExceptT, runExceptT, throwError)
import Control.Monad.Reader (ReaderT, ask, liftIO, runReaderT)
import Control.Monad.Except (ExceptT, MonadError, MonadTrans, runExceptT, throwError)
import Control.Monad.Reader (MonadIO, MonadReader, ReaderT, ask, liftIO, runReaderT)
import Control.Monad.Trans (MonadTrans (lift))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.HashMap.Strict (HashMap)
Expand All @@ -68,8 +78,28 @@ import Foreign.C.String
import GHC.Stack (emptyCallStack)
import OpenDAL.FFI

-- | `OperatorConfig` is the configuration for an `Operator`. Currently, it contains the scheme, config and log action.
-- Recommend using `OverloadedStrings` to construct a default config.
-- | `OperatorConfig` is the configuration for an `Operator`.
-- We recommend using `OverloadedStrings` to construct a default config.
--
-- For example:
--
-- default config
--
-- @
-- newOperator "memory"
-- @
--
-- custom services config
--
-- @
-- newOperator "memory" {ocConfig = HashMap.fromList [("root", "/tmp")]}
-- @
--
-- enable logging
--
-- @
-- newOperator "memory" {ocLogAction = Just simpleMessageAction}
-- @
data OperatorConfig = OperatorConfig
{ -- | The scheme of the operator. For example, "s3" or "gcs".
ocScheme :: String,
Expand All @@ -83,7 +113,7 @@ instance IsString OperatorConfig where
fromString s = OperatorConfig s HashMap.empty Nothing

-- | `Operator` is the entry for all public blocking APIs.
-- Create an `Operator` with `newOp`.
-- Create an `Operator` with `newOperator`.
newtype Operator = Operator (ForeignPtr RawOperator)

-- | `Lister` is designed to list entries at given path in a blocking manner.
Expand Down Expand Up @@ -149,8 +179,13 @@ data Metadata = Metadata
}
deriving (Eq, Show)

-- | The monad used for OpenDAL operations.
type OpMonad = ReaderT Operator (ExceptT OpenDALError IO)
-- | @newtype@ wrapper 'ReaderT' that keeps 'Operator' in its context.
newtype OperatorT m a = OperatorT
{runOperatorT :: ReaderT Operator (ExceptT OpenDALError m) a}
deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader Operator, MonadError OpenDALError)

instance MonadTrans OperatorT where
lift = OperatorT . lift . lift

-- | A type class for monads that can perform OpenDAL operations.
class (Monad m) => MonadOperation m where
Expand Down Expand Up @@ -188,7 +223,7 @@ class (Monad m) => MonadOperation m where
-- An error will be returned if given path doesn’t end with /.
scanOp :: String -> m Lister

instance MonadOperation OpMonad where
instance (MonadIO m) => MonadOperation (OperatorT m) where
readOp path = do
op <- ask
result <- liftIO $ readOpRaw op path
Expand Down Expand Up @@ -288,11 +323,30 @@ parseFFIMetadata (FFIMetadata mode cacheControl contentDisposition contentLength

-- Exported functions

-- | Runs an OpenDAL operation in the 'OpMonad'.
runOp :: Operator -> OpMonad a -> IO (Either OpenDALError a)
runOp operator op = runExceptT $ runReaderT op operator

-- | Creates a new OpenDAL operator via `HashMap`.
-- | Runner for 'OperatorT' monad.
-- This function will run given 'OperatorT' monad with given 'Operator'.
--
-- Let's see an example:
--
-- @
-- operation :: MonadOperation m => m ()
-- operation = __do__
-- writeOp op "key1" "value1"
-- writeOp op "key2" "value2"
-- value1 <- readOp op "key1"
-- value2 <- readOp op "key2"
-- @
--
-- You can run this operation with 'runOp' function:
--
-- @
-- runOp operator operation
-- @
runOp :: Operator -> OperatorT m a -> m (Either OpenDALError a)
runOp op = runExceptT . flip runReaderT op . runOperatorT
{-# INLINE runOp #-}

-- | Creates a new OpenDAL operator via `OperatorConfig`.
newOperator :: OperatorConfig -> IO (Either OpenDALError Operator)
newOperator (OperatorConfig scheme hashMap maybeLogger) = do
let keysAndValues = HashMap.toList hashMap
Expand Down Expand Up @@ -322,6 +376,7 @@ newOperator (OperatorConfig scheme hashMap maybeLogger) = do
str <- peekCString cStr
logger <& Msg (toEnum (fromIntegral enumSeverity)) emptyCallStack (pack str)

-- $raw-operations
-- Functions for performing raw OpenDAL operations are defined below.
-- These functions are not meant to be used directly in most cases.
-- Instead, use the high-level interface provided by the 'MonadOperation' type class.
Expand Down
1 change: 0 additions & 1 deletion bindings/haskell/haskell-src/OpenDAL/FFI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
{-# LANGUAGE ForeignFunctionInterface #-}

module OpenDAL.FFI where

Expand Down
36 changes: 20 additions & 16 deletions bindings/haskell/opendal-hs.cabal
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
cabal-version: 2.2
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
Expand All @@ -15,7 +16,6 @@
-- specific language governing permissions and limitations
-- under the License.

cabal-version: 2.0
name: opendal-hs
version: 0.1.0.0
license: Apache-2.0
Expand All @@ -29,14 +29,7 @@ source-repository head
type: git
location: https://github.com/apache/incubator-opendal

library
exposed-modules:
OpenDAL
other-modules:
OpenDAL.FFI
hs-source-dirs: haskell-src
default-language: Haskell2010
extra-libraries: opendal_hs
common base
ghc-options: -Wall
build-depends:
base >=4.10 && <4.17,
Expand All @@ -46,19 +39,30 @@ library
time >=1.10,
co-log >=0.5,
text >=2
default-language: Haskell2010
default-extensions:
OverloadedStrings,
ForeignFunctionInterface,
DerivingStrategies,
GeneralizedNewtypeDeriving,
LambdaCase

library
import: base
extra-libraries: opendal_hs
exposed-modules:
OpenDAL
other-modules:
OpenDAL.FFI
hs-source-dirs: haskell-src

test-suite opendal-hs-test
import: base
type: exitcode-stdio-1.0
main-is: Spec.hs
other-modules: BasicTest
hs-source-dirs: test
default-language: Haskell2010
other-extensions: OverloadedStrings
ghc-options: -Wall
build-depends:
base,
opendal-hs,
tasty,
tasty-hunit,
co-log,
text
tasty-hunit
2 changes: 0 additions & 2 deletions bindings/haskell/test/BasicTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module BasicTest (basicTests) where

Expand Down
Loading