This is an automated email from the ASF dual-hosted git repository. silver pushed a commit to branch feat/haskell-operator-transformer in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
commit 263517369921b72cf71ec4074b963b6bc5871144 Author: silver-ymz <[email protected]> AuthorDate: Sun Aug 6 18:10:38 2023 +0800 feat(bindings/haskell): enhance original `OpMonad` to support custom IO monad Signed-off-by: silver-ymz <[email protected]> --- bindings/haskell/cabal.project.local | 18 ------ bindings/haskell/haskell-src/OpenDAL.hs | 89 +++++++++++++++++++++++------ bindings/haskell/haskell-src/OpenDAL/FFI.hs | 1 - bindings/haskell/opendal-hs.cabal | 34 ++++++----- bindings/haskell/test/BasicTest.hs | 2 - 5 files changed, 91 insertions(+), 53 deletions(-) diff --git a/bindings/haskell/cabal.project.local b/bindings/haskell/cabal.project.local deleted file mode 100644 index b72cb7b11..000000000 --- a/bindings/haskell/cabal.project.local +++ /dev/null @@ -1,18 +0,0 @@ --- 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 --- regarding copyright ownership. The ASF licenses this file --- to you under the Apache License, Version 2.0 (the --- "License"); you may not use this file except in compliance --- with the License. You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, --- software distributed under the License is distributed on an --- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY --- KIND, either express or implied. See the License for the --- specific language governing permissions and limitations --- under the License. - -tests: True \ No newline at end of file diff --git a/bindings/haskell/haskell-src/OpenDAL.hs b/bindings/haskell/haskell-src/OpenDAL.hs index 773901f5a..cb2eb7fdf 100644 --- a/bindings/haskell/haskell-src/OpenDAL.hs +++ b/bindings/haskell/haskell-src/OpenDAL.hs @@ -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 @@ -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, @@ -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) @@ -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, @@ -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. @@ -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 (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 @@ -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 @@ -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 @@ -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. diff --git a/bindings/haskell/haskell-src/OpenDAL/FFI.hs b/bindings/haskell/haskell-src/OpenDAL/FFI.hs index 40f993fec..ad28f451c 100644 --- a/bindings/haskell/haskell-src/OpenDAL/FFI.hs +++ b/bindings/haskell/haskell-src/OpenDAL/FFI.hs @@ -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 diff --git a/bindings/haskell/opendal-hs.cabal b/bindings/haskell/opendal-hs.cabal index 892a2d7d4..d5aa95e88 100644 --- a/bindings/haskell/opendal-hs.cabal +++ b/bindings/haskell/opendal-hs.cabal @@ -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 @@ -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 @@ -29,13 +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 +common base extra-libraries: opendal_hs ghc-options: -Wall build-depends: @@ -46,19 +40,29 @@ library time >=1.10, co-log >=0.5, text >=2 + default-language: Haskell2010 + default-extensions: + OverloadedStrings, + ForeignFunctionInterface, + DerivingStrategies, + GeneralizedNewtypeDeriving, + LambdaCase + +library + import: base + 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 \ No newline at end of file + tasty-hunit \ No newline at end of file diff --git a/bindings/haskell/test/BasicTest.hs b/bindings/haskell/test/BasicTest.hs index dcf16d6d0..77e0a6586 100644 --- a/bindings/haskell/test/BasicTest.hs +++ b/bindings/haskell/test/BasicTest.hs @@ -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
