Skip to content

Commit

Permalink
Overhaul the API.
Browse files Browse the repository at this point in the history
Now, functions which create immutable datatypes are pure.
Added a note to Base with a caveat about pointer comparison.
  • Loading branch information
judah committed Aug 25, 2011
1 parent 20cc0ab commit 6b6cdef
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 83 deletions.
7 changes: 4 additions & 3 deletions CoreFoundation/System/CoreFoundation/Array.chs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module System.CoreFoundation.Array(
getCount,
getObjectAtIndex,
-- * Creating arrays
newArray,
fromList,
) where


Expand Down Expand Up @@ -50,8 +50,9 @@ foreign import ccall "&" kCFTypeArrayCallBacks :: Ptr ()
, id `Ptr ()'
} -> `Array' getOwned* #}

newArray :: Object a => [a] -> IO Array
newArray objs = withObjects objs $ \ps ->
-- | Returns a new immutable 'Array' which contains the elements of the given list.
fromList :: Object a => [a] -> Array
fromList objs = unsafePerformIO $ withObjects objs $ \ps ->
withArrayLen ps $ \ n p ->
cfArrayCreate p n kCFTypeArrayCallBacks

Expand Down
27 changes: 26 additions & 1 deletion CoreFoundation/System/CoreFoundation/Base.chs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ module System.CoreFoundation.Base(
CFTypeRef,
Object(),
touchObject,
-- ** Foreign interaction with 'CFTypeRef's
-- $foreign
withObject,
withObjects,
-- ** Foreign interaction with 'CFTypeRef's
getOwned,
getAndRetain,
maybeGetOwned,
Expand Down Expand Up @@ -57,6 +58,30 @@ foreign import ccall "CFRelease" cfRelease :: CFTypeRef -> IO ()
touchObject :: Object a => a -> IO ()
touchObject = touchForeignPtr . unsafeUnObject


{- $foreign
We note one caveat about the foreign export functions. Namely, the pure
object constructors like @String.fromChars@ and @Number.newNumber@ break
referential transparency if the underlying 'CFTypeRef's are tested for equality. For
example:
> -- Returns False
> test1 = let
> str1 = fromChars "foo"
> str2 = fromChars "foo"
> in withObject str1 $ \p1 -> withObject str2 $ \p2 -> return p1==p2
>
> -- Returns True
> test1 = let
> str = fromChars "foo"
> in withObject str $ \p1 -> withObject str $ \p2 -> return p1==p2
In general, however, this should not cause problems when using the
Core Foundation API functions.
-}

-- | Like 'withForeignPtr', extracts the underlying C type and keeps the object alive
-- while the given action is running.
-- It is not safe in general to use the 'CFTypeRef' after the action completes.
Expand Down
9 changes: 4 additions & 5 deletions CoreFoundation/System/CoreFoundation/Data.chs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ module System.CoreFoundation.Data(
Data,
DataRef,
getLength,
withData,
newData,
fromByteString,
getByteString,
) where

Expand Down Expand Up @@ -58,7 +57,7 @@ withData d f = do
getByteString :: Data -> IO B.ByteString
getByteString d = withData d $ \p n -> B.packCStringLen (castPtr p, cvtEnum n)

-- | Makes a new Data object with a copy of the ByteString's data.
newData :: B.ByteString -> IO Data
newData b = UnsafeB.unsafeUseAsCStringLen b $ \(p,len) ->
-- | Makes a new immutable Data object with a copy of the ByteString's data.
fromByteString :: B.ByteString -> Data
fromByteString b = unsafePerformIO $ UnsafeB.unsafeUseAsCStringLen b $ \(p,len) ->
createData (castPtr p) (toEnum len)
9 changes: 5 additions & 4 deletions CoreFoundation/System/CoreFoundation/Dictionary.chs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module System.CoreFoundation.Dictionary(
getValue,
getValueOfType,
-- * Creating dictionaries
newDictionary,
fromKeyValues,
) where

import Foreign
Expand Down Expand Up @@ -52,9 +52,10 @@ foreign import ccall "&" kCFTypeDictionaryValueCallBacks :: Ptr ()
, id `Ptr ()'
} -> `Dictionary' getOwned* #}

newDictionary :: (Object key, Object value) => [(key,value)]
-> IO Dictionary
newDictionary kvs = do
-- | Create a new immutable 'Dictionary' whose keys and values are taken from the given
-- list.
fromKeyValues :: (Object key, Object value) => [(key,value)] -> Dictionary
fromKeyValues kvs = unsafePerformIO $ do
let (keys,values) = unzip kvs
withObjects keys $ \ks -> do
withArrayLen ks $ \n pks -> do
Expand Down
9 changes: 8 additions & 1 deletion CoreFoundation/System/CoreFoundation/Error.chs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module System.CoreFoundation.Error (
-- * The Error type
Error,
ErrorRef,
newError,
cfError,
-- * Error properties
errorDescription,
errorFailureReason,
Expand Down Expand Up @@ -57,6 +57,13 @@ importCFStringAs "kCFErrorDomainCocoa" "domainCocoa"
, maybeWithObject* `Maybe Dictionary'
} -> `Error' getOwned* #}

cfError :: String -- ^ The error domain.
-> Int -- ^ The error code.
-> Maybe Dictionary -- ^ User info.
-> Error
cfError domain code userInfo = unsafePerformIO
$ newError domain code userInfo

maybeWithObject Nothing = ($ nullPtr)
maybeWithObject (Just o) = withObject o

Expand Down
35 changes: 8 additions & 27 deletions CoreFoundation/System/CoreFoundation/Number.chs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
module System.CoreFoundation.Number(
Number,
NumberRef,
newNumber,
unsafeNumber,
numberValue,
number,
value,
numberType,
IsNumberType,
NumberType(..),
Expand All @@ -27,7 +26,7 @@ declareCFType "Number"

-- | Returns whether the 'Number' contains a value stored internally
-- as one of the floating point types.
{#fun CFNumberIsFloatType as isFloatType
{#fun pure CFNumberIsFloatType as isFloatType
{ withObject* `Number' } -> `Bool' '(>0)' #}

{#enum define NumberType
Expand All @@ -51,7 +50,7 @@ declareCFType "Number"

-- Returns the type used by the 'Number' object to store its value.
--
-- The type specified by 'newNumber' is not necessarily preserved when
-- The type specified by 'number' is not necessarily preserved when
-- a new 'Number' is created --- it uses whatever internal storage type
-- it deems appropriate.
{#fun pure unsafe CFNumberGetType as numberType
Expand Down Expand Up @@ -111,8 +110,8 @@ instance IsNumberType Int where
-- TODO: error checking. (Currently it will do lossy conversion.)

-- | Gets the value in the Number, cast to a specific type.
numberValue :: forall a . IsNumberType a => Number -> a
numberValue n = unsafePerformIO $ alloca $ \p -> do
value :: forall a . IsNumberType a => Number -> a
value n = unsafePerformIO $ alloca $ \p -> do
getNumberValue n (numberTypeOf (undefined :: a)) p
peek p

Expand All @@ -122,24 +121,6 @@ numberValue n = unsafePerformIO $ alloca $ \p -> do
, castPtr `Ptr a'
} -> `Number' getOwned* #}

newNumber :: forall a . IsNumberType a => a -> IO Number
newNumber n = with n $
number :: forall a . IsNumberType a => a -> Number
number n = unsafePerformIO $ with n $
numberCreate (numberTypeOf (undefined :: a))

-- | Creates a new 'Number' object.
--
-- This function is unsafe since it breaks referential transparency
-- when the result's underlying 'CFTypeRef' is tested for equality.
-- For example, in the below code @test1@ returns @False@ but @test2@ returns @True@.
--
-- > test1 = do
-- > p <- retainCFTypeRef $ unsafeNumber (0::Int)
-- > q <- retainCFTypeRef $ unsafeNumber (0::Int)
-- > return (p==q)
-- > test2 = do
-- > let m = unsafeNumber (0::Int)
-- > p <- retainCFTypeRef m
-- > q <- retainCFTypeRef m
-- > return (p==q)
unsafeNumber :: IsNumberType a => a -> Number
unsafeNumber = unsafePerformIO . newNumber
22 changes: 11 additions & 11 deletions CoreFoundation/System/CoreFoundation/Preferences.chs
Original file line number Diff line number Diff line change
Expand Up @@ -60,37 +60,37 @@ instance Preference Prelude.String where
toPreference p = toPreference p `thenMaybe` (fmap Just . getChars)

instance Preference Int8 where
toPreference = fmap (fmap numberValue) . toPreference
toPreference = fmap (fmap value) . toPreference

instance Preference Int16 where
toPreference = fmap (fmap numberValue) . toPreference
toPreference = fmap (fmap value) . toPreference

instance Preference Int32 where
toPreference = fmap (fmap numberValue) . toPreference
toPreference = fmap (fmap value) . toPreference

instance Preference CChar where
toPreference = fmap (fmap numberValue) . toPreference
toPreference = fmap (fmap value) . toPreference

instance Preference CShort where
toPreference = fmap (fmap numberValue) . toPreference
toPreference = fmap (fmap value) . toPreference

instance Preference CInt where
toPreference = fmap (fmap numberValue) . toPreference
toPreference = fmap (fmap value) . toPreference

instance Preference CLong where
toPreference = fmap (fmap numberValue) . toPreference
toPreference = fmap (fmap value) . toPreference

instance Preference CLLong where
toPreference = fmap (fmap numberValue) . toPreference
toPreference = fmap (fmap value) . toPreference

instance Preference CFloat where
toPreference = fmap (fmap numberValue) . toPreference
toPreference = fmap (fmap value) . toPreference

instance Preference CDouble where
toPreference = fmap (fmap numberValue) . toPreference
toPreference = fmap (fmap value) . toPreference

instance Preference Int where
toPreference = fmap (fmap numberValue) . toPreference
toPreference = fmap (fmap value) . toPreference

thenMaybe :: Monad m => m (Maybe a) -> (a -> m (Maybe b)) -> m (Maybe b)
thenMaybe f g = f >>= \mx -> case mx of
Expand Down
38 changes: 8 additions & 30 deletions CoreFoundation/System/CoreFoundation/String.chs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ module System.CoreFoundation.String(
newStringFromExternalRepresentation,
StringEncoding(..),
-- * Conversion to/from 'Prelude.String'
newStringFromChars,
fromChars,
getChars,
unsafeStringFromChars,
-- * Conversion to/from 'Text'
newStringFromText,
fromText,
getText,
-- * Foreign import of string constants
importCFString,
Expand Down Expand Up @@ -87,37 +86,16 @@ declareCFType "String"



-- | Create a new @CoreFoundation.String@ which contains a copy of the given 'Text'.
newStringFromText :: Text.Text -> IO String
newStringFromText t = useAsPtr t $ \p len ->
-- | Create a new immutable @CoreFoundation.String@ which contains a copy of the given 'Text'.
fromText :: Text.Text -> String
fromText t = unsafePerformIO $ useAsPtr t $ \p len ->
createStringWithBytes (castPtr p)
(cvtEnum $ 2*len) UTF16
False -- Text doesn't add a BOM

-- | Create a new @CoreFoundation.String@ which contains a copy of the given @Prelude.String@.
newStringFromChars :: Prelude.String -> IO String
newStringFromChars = newStringFromText . Text.pack

-- | Create a new @CoreFoundation.String@ which contains a copy of the given @Prelude.String@.
--
-- This function is unsafe since it breaks referential transparency
-- when the result's underlying 'CFTypeRef' is tested for equality.
-- For example, in the below code @test1@ returns @False@ but @test2@ returns @True@.
--
-- > test1 = do
-- > p <- retainCFTypeRef $ unsafeStringFromChars "foo"
-- > q <- retainCFTypeRef $ unsafeStringFromChars "foo"
-- > return (p==q)
-- > test2 = do
-- > let s = unsafeStringFromChars "foo"
-- > p <- retainCFTypeRef s
-- > q <- retainCFTypeRef s
-- > return (p==q)
unsafeStringFromChars :: Prelude.String -> String
unsafeStringFromChars = unsafePerformIO . newStringFromChars

-- As an aside, we don't really need to worry about mutability as regards
-- the safety of the above function, since it only returns immutable strings.
-- | Create a new immutable @CoreFoundation.String@ which contains a copy of the given @Prelude.String@.
fromChars :: Prelude.String -> String
fromChars = fromText . Text.pack

-- | Extract a 'Text' copy of the given @CoreFoundation.String@.
getText :: String -> IO Text.Text
Expand Down
2 changes: 1 addition & 1 deletion examples/ExpressionEvaluator/haskell/ForeignExpr.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ processInputHelper input = do
let resultStr = case parse expr "<input>" inputStr of
Left err -> show err
Right result -> show result
newStringFromChars resultStr
return $ fromChars resultStr

0 comments on commit 6b6cdef

Please sign in to comment.