In your example, you think that you are passing an XPath expression as the third argument, but you aren't - you are passing its value.
In XPath 3.0 there is a clean solution to this problem: you can pass a function as the third argument. So the call becomes
select="my:doit('item', /foo/yours:pet, function($e){$e/@my:color})"
and the implementation becomes
<xsl:function name="my:doit" as="xs:string?">
<xsl:param name="item" as="xs:string"/>
<xsl:param name="value" as="element()*/>
<xsl:param name="subvalue" as="function(element()) as xs:string?"/>
<xsl:if test="$value">
<xsl:value-of>
<xsl:value-of select="$item"/>
<xsl:text>=</xsl:text>
<xsl:value-of select="$value ! $subvalue(.)" separator=";"/>
</xsl:value-of>
</xsl:if>
</xsl:function>
If you're not in a position to use XPath 3.0 (the above will work in Saxon-PE 9.5) then alternatives are (a) to use Dimitre Novatchev's FXSL library, which simulates higher-order functions in XSLT 2.0, or (b) instead of passing a function, pass an XPath expression in the form of a string, and use a xx:eval() extension function to evaluate the expression dynamically - many processors offer such an extension, but it's not in the standard language.
(Note, in XPath 3.0, A!B means essentially the same as "for $x in A return $x/B")