A function with the type SomeType -> SomeType -> SomeType looks like a candidate for a Semigroup, or at least something that can be implemented with Semigroup. There's a couple of options.
Explicit merge
If you keep SomeType as in the OP, you could write an explicit merge function like this:
merge :: SomeType -> SomeType -> SomeType
merge x y = toSomeType $ toTriple x <> toTriple y
where
toTriple (SomeType a b c) = (Last <$> a, Last <$> b, c)
toSomeType (a, b, c) = SomeType (getLast <$> a) (getLast <$> b) c
This converts each SomeType instance to a triple (three-tuple), for which a Semigroup instance exists if all three elements have Semigroup instances.
There's more than one Semigroup instance for Maybe, but (from GHC 8.4) any Maybe a is a Semigroup (and Monoid) instance when a is a Semigroup instance.
The Semigroup instance that favours the last of two values is Last, so toTriple maps a and b to Maybe Last values. It doesn't, however, map c, because in this implementation it assumes that OtherType is already a Semigroup instance (see below).
Since the resulting triples are themselves Semigroup instances, they can be combined with the <> operator. This gives you a resulting triple that you can convert back to a SomeType value with toSomeType.
Semigroup instances
You could also simply make the types themselves Semigroup instances. I think this is best if there's no ambiguity. In itself, Maybe can be more than one Semigroup instance, e.g. favouring the First or Last value, respectively.
If you always want to favour the Last value, however, you can make this explicit in the type. Here's one way OtherType could look:
data OtherType =
OtherType { foo :: Maybe (Last Int), bar :: Maybe (Last String) } deriving (Eq, Show)
Notice that the fields aren't just Maybe values, but explicitly Maybe Last values. This gives rise to an unambiguous Semigroup instance:
instance Semigroup OtherType where
(OtherType foo1 bar1) <> (OtherType foo2 bar2) =
OtherType (foo1 <> foo2) (bar1 <> bar2)
You could also follow the same design principle for SomeType, which would make the explicit merge function redundant.
Examples
You can try out the above functionality in GHCi:
*Q54068475> merge st1 st2
SomeType {a = Just 1, b = Just "world", c = Nothing}
*Q54068475> ot1 = OtherType (Just (Last 42)) (Just (Last "foo"))
*Q54068475> ot2 = OtherType (Just (Last 1337)) Nothing
*Q54068475> merge (SomeType (Just 1) (Just "hello") (Just ot1))
(SomeType Nothing (Just "world") (Just ot2))
SomeType {a = Just 1,
b = Just "world",
c = Just (OtherType {foo = Just (Last {getLast = 1337}),
bar = Just (Last {getLast = "foo"})})}
(I've added a few line breaks to the GHCi session to make it more readable...)
Monoids
These types could also be Monoid instances:
instance Monoid OtherType where
mempty = OtherType Nothing Nothing
This could come in useful, so you might as well consider adding those instances as well...