2015-10-25 2 views
3

В поисках Polyvariadic примеров функций, я нашел этот ресурс: StackOverflow: How to create a polyvariadic haskell function?, и был ответ фрагмент кода, как это:Haskell, polyvariadic функции и определение типа

class SumRes r where 
    sumOf :: Integer -> r 

instance SumRes Integer where 
    sumOf = id 

instance (Integral a, SumRes r) => SumRes (a -> r) where 
    sumOf x = sumOf . (x +) . toInteger 

Тогда мы могли бы использовать:

*Main> sumOf 1 :: Integer 
1 
*Main> sumOf 1 4 7 10 :: Integer 
22 
*Main> sumOf 1 4 7 10 0 0 :: Integer 
22 
*Main> sumOf 1 4 7 10 2 5 8 22 :: Integer 
59 

Я попробовал изменить его немного, просто из любопытства, потому что я нашел, что это довольно сложно, на первый взгляд, и я попал в это:

class SumRes r where 
    sumOf :: Int -> r 

instance SumRes Int where 
    sumOf = id 

instance (SumRes r) => SumRes (Int -> r) where 
    sumOf x = sumOf . (x +) 

Я только что изменил Integer к Int и превратили instance (Integral a, SumRes r) => SumRes (a -> r) where менее полиморфный к instance (SumRes r) => SumRes (Int -> r) where

Чтобы скомпилировать его я должен был установить XFlexibleInstances флаг. Когда я судимый испытать sumOf функции У меня проблема:

*Main> sumOf 1 :: Int 
1 
*Main> sumOf 1 1 :: Int 
<interactive>:9:9 
    No instance for (Num a0) arising from the literal `1' 
    The type variable `a0' is ambiguous... 

Тогда я попробовал:

*Main> sumOf (1 :: Int) (1 :: Int) :: Int 
2 

Почему не Haskell сделать вывод, что мы хотим Int в этой ситуации, учитывая, что мы используем Int в пределах нашего SumRes typeclass?

+0

Похоже на дефолт (предполагается, что '1' означает, что' Int' 1) запускается после разрешения экземпляра в 'ghci'. Справедливо. Вы можете добавить отдельную группу экземпляров шага для «Integer» вместо «Int», и тогда у вас будет реальная двусмысленность. – pigworker

+0

Вы можете прочитать [этот вопрос и мой ответ на него] (http://stackoverflow.com/q/10777283/791604) для некоторых объяснений в другом направлении и полезный трюк, который вы можете использовать для решения проблемы неверный вывод здесь: используйте 'instance a ~ Int => SumRes (a -> r)' вместо 'instance SumRes (Int -> r)'. –

+0

Чтобы прояснить раздел о дефолте, 'Int' не является типом, для которого обычно используется значение по умолчанию, а' Integer'. Поэтому исходный код работает, по умолчанию все литералы «Integer». –

ответ

4

Экземпляр

instance (...) => SumRes (Int -> r) where 

примерно означает "вот как определить SumRes на Int -> r для любого r (при определенных условиях)". Сравните это с

instance (...) => SumRes (a -> r) where 

, что означает «вот как определить SumRes на a -> r для любого a,r (при определенных условиях)».

Главное отличие состоит в том, что во втором говорится, что это Соответствующий экземпляр в зависимости от типа a,r может быть. Запрещая некоторое (очень сложное и потенциально опасное) расширение Haskell, нельзя добавлять дополнительные экземпляры позже с привлечением функций. Вместо этого первый оставляет место для новых экземпляров, таких как, например,

instance (...) => SumRes (Double -> r) where ... 
instance (...) => SumRes (Integer -> r) where ... 
instance (...) => SumRes (Float -> r) where ... 
instance (...) => SumRes (String -> r) where ... -- nonsense, but allowed 

Это в сочетании с тем, что числовые литералы, такие как 5 полиморфны: их тип должен быть выведен из контекста. Поскольку позже компилятор может найти, например, a Double -> r и выберите Double в качестве литерала, компилятор не связывается с экземпляром Int -> r и сообщает о двусмысленности в ошибке типа.

Обратите внимание, что, используя некоторые (безопасные) расширения Haskell (например, TypeFamilies), можно «пообещать» компилятору, что ваш Int -> r является единственным, который будет объявлен во всей программе. Это делается так:

instance (..., a ~ Int) => SumRes (a -> r) where ... 

Это обещает обрабатывать все «функционального типа» случаи, но требует, чтобы a на самом деле тот же тип Int.

+0

Это был именно тот момент. Когда вы сказали **, вот как определить 'SumRes' на' Int -> r' для любого r (при определенных условиях) **, это означает, что хотя мы объясняем компилятору в этом конкретном случае, это могут быть другие способы например, 'Double -> r'. Другой вопрос: почему не 'TypeFamilies' по умолчанию? Есть ли веские причины? – FtheBuilder

+2

@FtheBuilder С несколькими заметными исключениями GHC пытается выполнить [Отчет] (https://www.haskell.org/onlinereport/haskell2010/) по умолчанию, а типы семейств не входят в отчет. GHC, вероятно, единственная реализация, которая их поддерживает; и поскольку их поддержка может рассматриваться как существенное бремя осуществления, я подозреваю, что они не станут частью Отчета в ближайшем будущем. Тем не менее, я не считал бы 'TypeFamilies' спорным или«неприглядное»расширения; если вам это нравится, и планируете использовать GHC, используйте его безнаказанно. –

3

Количество литералы сами по себе являются полиморфными, а не типа Int

*Main> :t 1 
1 :: Num a => a 

Посмотрите, что происходит, когда мы получаем сигнатуру типа:

*Main> :t sumOf 1 2 3 
sumOf 1 2 3 :: (Num a, Num a1, SumRes (a -> a1 -> t)) => t 

Обратите внимание, что тип не упоминает Int вообще. Средство проверки типов не может определить, как реально вычислить сумму, потому что ни один из определенных Int экземпляров не является достаточно общим для применения здесь.

Если вы фиксируете типы быть Int, то вы в конечном итоге с

*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int) 
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: SumRes t => t 

*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int 
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int 

Обратите внимание, что SumRes t => t совместим с Int, потому что у нас есть SumRes Int экземпляр, но если мы не будем явно указывать Int, то у нас нет примеров, достаточно общих для применения здесь, потому что нет общего экземпляра SumRes t.