Список полиморфных функций в haskell?

Рассмотрим код ниже:

t1 :: [Int] -> (Int,String)
t1 xs = (sum xs,show $ length xs)

t2 :: [Int] -> (Int,String)
t2 xs = (length xs, (\x -> '?') <$> xs)

t3 :: [Int] -> (Char,String)
t3 (x:xs) = ('Y',"1+" ++ (show $ length xs))
t3  []     = ('N',"empty")

Эти три функции имеют тип, который изменяется лишь частично — их можно полностью использовать без необходимости знать тип первого компонента создаваемого ими кортежа. Это означает, что я могу работать с ними, не обращаясь к этому типу:

fnListToStrs vs fs = (\x -> snd $ x vs) <$> fs

При загрузке этих определений в GHCi все три функции работают независимо в качестве аргумента fnListToStrs, и действительно, я могу передать список, содержащий как t1, так и t2, потому что они имеют один и тот же тип:

*Imprec> fnListToStrs [1,2] [t1,t2]
["2","??"]
*Imprec> fnListToStrs [1,2] [t3]
["1+1"]

Но я не могу передать все 3 одновременно, хотя расхождение типов фактически не имеет отношения к выполненному расчету:

*Imprec> fnListToStrs [1,2] [t1,t2]
["2","??"]
*Imprec> fnListToStrs [1,2] [t3]
["1+1"]

У меня есть ощущение, что выполнение этой работы имеет какое-то отношение либо к экзистенциальным, либо к непредикативным типам, но ни одно из расширений не сработало для меня при использовании объявления типа, которое, как я ожидаю, может принять fnListToStrs, а именно:

fnListToStrs :: [Int] -> [forall a.[Int]->(a,String)] -> [String]

Есть ли другой способ сделать эту работу?


person Jules    schedule 22.03.2016    source источник


Ответы (2)


Экзистенциальное правильно, а не непредикативно. И у Haskell нет экзистенциалов, кроме как через явную оболочку...

{-# LANGUAGE GADTs #-}

data SomeFstRes x z where
  SFR :: (x -> (y,z)) -> SomeFstRes x z

> fmap (\(SFR f) -> snd $ f [1,2]) [SFR t1, SFR t2, SFR t3]
["2","??","1+1"]

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

> fmap ($[1,2]) [snd . t1, snd . t2, snd . t3]
["2","??","1+1"]
person leftaroundabout    schedule 22.03.2016
comment
Возможно, я немного упростил ситуацию в своем вопросе, из-за чего я не уверен, как это применить на самом деле. В моем реальном сценарии функции уже заключены в тип, но оба типа результатов выставляются как параметры для типа. Мне фактически нужно составить список, игнорирующий один из параметров, и, поскольку я пытаюсь создать упрощенный API, я бы предпочел, чтобы клиентам не нужно было использовать явную оболочку. Но, похоже, это невозможно... - person Jules; 22.03.2016

Любой способ поместить эти функции в список потребует некоторого "обертывания" каждой из них. Самая простая обертка

wrap :: (a -> (b, c)) -> a -> c
wrap f = snd . f

На самом деле существуют и другие способы обернуть их (особенно с помощью экзистенциальных типов), но вы не предоставили никакой информации, позволяющей предположить, что какой-либо из них будет даже немного лучше в вашем приложении, чем эта простейшая версия.

Вот пример, когда что-то более сложное может иметь смысл. Предположим, у вас есть

data Blob a b = Blob [a -> b] [a]

Теперь представьте, что вы хотите составить список значений типа Blob a b, которые имеют один и тот же тип b, но могут иметь разные типы a. На самом деле применение каждой функции к каждому аргументу может привести к чрезмерно большому списку возможных результатов, поэтому имеет смысл написать

data WrapBlob b where
  WrapBlob :: Blob a b -> WrapBlob b

Теперь вы можете составить список и отложить решение о том, какие функции применить к какому аргументу (аргументам), не заплатив непомерно высокую цену.

person dfeuer    schedule 22.03.2016