The William Cook paper 'On Understanding Data Abstraction' gives a basic formulation of pure objects. Crucially, an 'object' needn't be what the language itself calls an object, e.g. I could write up a 'set object' in Haskell, which does not have objects:
data Set elem = Set { contains :: elem -> Bool }
insert :: (Eq elem) => elem -> Set elem -> Set elem
insert key set = Set { contains = c }
where c key' = if key == key' then True else contains set key
union :: (Eq elem) => Set elem -> Set elem -> Set elem
union set1 set2 = Set { contains = c }
where c key = contains set1 key || contains set2 key
emptySet :: Set elem
emptySet = Set { contains = (\ _ -> False) }
-- mySet corresponds to {1,2,3}
mySet :: Set Int
mySet = insert 3 (insert 2 (insert 1 emptySet))
-- myVal is True
myVal :: Bool
myVal = contains mySet 2
Depending on your definition of object-oriented programming, this could be considered an object or something else. William Cook argues that it's an object because access depends only on a public interface, i.e. I could add another implementation
fromList :: (Eq elem) => [elem] -> Set elem
fromList list = Set { contains = c}
where c key = key `elem` list
which uses a different representation underneath the surface, but the previous implementations of union and insert will work with it because it exposes the same interface... which is not the case with ML/OCaml modules, which must select a single implementation. This is a simplified version of the example Cook himself uses, so I urge you to read the paper if you want to understand more. (On the other hand, people in the Smalltalk/Ruby/&c camp will say, "No, of course that's not object-oriented—you're not sending messages!" So... it's a complicated debate.)
[1]: http://www.cs.utexas.edu/~wcook/Drafts/2009/essay.pdf also linked by https://news.ycombinator.com/item?id=6084465