Краткий ответ: это происходит потому, что компилятор имеет доступ к более конкретной информации о типах в первом случае, чем во втором.
При компиляции вашего определения arr
компилятор видит только тип аргумента функции f
как b -> c
, поэтому при рассмотрении вызова toStr f
он должен выбрать экземпляр на основе только этой информации. В конце концов, arr
может быть вызван с любой функцией. Понятно, что выбирать можно только instance ToPredefinedStr (a -> b)
.
Теперь, когда мы встроим его, как в toStr (\b -> (b, b))
, компилятор имеет больше информации, доступной на сайте вызовов, и может выбрать более конкретный экземпляр.
И нет, использование INLINE
прагм не изменит выбор экземпляра, если вы об этом думаете.
Для того, что вы пытаетесь достичь, самое близкое, что я могу придумать, это ограничить типы так, чтобы выбор экземпляра происходил снаружи arr
:
{-# LANGUAGE FlexibleContexts, ... #-}
class FancyArrow a where
myArr :: (ToPredefinedStr (b -> c)) => (b -> c) -> a b c
...
instance (Arrow a) => FancyArrow (MyArrow a) where
myArr f = MA (arr (\x -> (f x, toStr f)))
Это дает желаемый результат.
*Main> appMyArr (myArr (\x -> (x,x))) ()
(((),()),"b -> (b,b)")
Обратите внимание, что это несколько хрупко, так как вы должны контролировать, где делается выбор экземпляра, распространяя ограничение ToPredefinedStr
. Например, эта функция молча меняет поведение, если вы удаляете сигнатуру типа.
foo :: (Arrow a, ToPredefinedStr (b -> c)) => (b -> c) -> a b (c, String)
foo f = appMyArr (myArr f)