GHC выбирает разные экземпляры для одного и того же выражения?

Я хочу реализовать стрелку с arr-функцией-членом, показывающей различное поведение для аргументов функции с разными типами, например, arr (\x -> (x,x)) должно вести себя иначе, чем arr id...

Вот код:

{-# LANGUAGE Arrows, OverlappingInstances, IncoherentInstances, FlexibleInstances#-}
import Control.Arrow
import Control.Category 
import Prelude hiding (id, (.))

class ToPredefinedStr a where
  toStr :: a -> String


instance ToPredefinedStr ((->) b (b,b)) where
  toStr _ = "b -> (b,b)"

instance ToPredefinedStr (a -> (b,c)) where
  toStr _ = "a -> (b,c)" 

instance ToPredefinedStr ((a,b) -> c) where
  toStr _ = "(a,b) -> c"

instance ToPredefinedStr (a -> b) where 
  toStr _ = "a -> b"

newtype MyArrow a b c = MA (a b (c, String))

instance (Category a, Arrow a) => Category (MyArrow a) where
    -- irrelevant for this example ...

instance (Arrow a) => Arrow (MyArrow a) where
    arr f        = MA (arr (\x -> (f x, toStr f)))

appMyArr (MA a) = a

Но: Он показывает следующее очень странное поведение:

> toStr (\x -> (x,x)) -- that works as expected!
"b -> (b,b)" 
> appMyArr (arr (\x -> (x,x))) () -- but this does'nt!!
(((),()),"a -> b")

Кто-нибудь может объяснить, как заставить ghci выбрать экземпляр b -> (b,b) для выражения \x -> (x,x) во втором примере?


person phynfo    schedule 13.07.2011    source источник


Ответы (2)


Короткий ответ заключается в том, что это происходит потому, что компилятор имеет доступ к более конкретной информации о типе в первом случае, чем во втором.

При компиляции вашего определения 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)
person hammar    schedule 13.07.2011
comment
Интересное предложение; Завтра обязательно попробую! ... Почему вы думаете, что это не сработает? - person phynfo; 13.07.2011
comment
В основном потому, что он не будет работать со стандартным классом Arrow, но если вас это устраивает, то, конечно, он должен работать. Тем не менее, он чувствует себя несколько хрупким. - person hammar; 14.07.2011

Если вы используете IncoherentInstances, может случиться что угодно. Больше нет никаких обещаний, что экземпляры выбираются последовательным образом.

person augustss    schedule 13.07.2011
comment
Я думал, IncoherentInstances будет идти сверху вниз, выбирая первый подходящий экземпляр...? - person phynfo; 13.07.2011
comment
@phynfo: первый экземпляр, который соответствует заданным типам для контекста, из которого он ищет. Мне неясны детали, и руководство пользователя GHC несколько неразговорчиво по этому поводу, но ответ Хаммара звучит правдоподобно. Но на самом деле это бессвязно. Это сказано прямо в названии. Что вы ожидали? :] - person C. A. McCann; 13.07.2011