Вы уже поняли, почему это так хлопотно.Ваша функция (@@)
применяется к входам различных типов (например, [x->y]
, [[x -> y]]
и т. Д.). Это означает, что ваша сигнатура типа для @@
является слишком строгой ; вам необходимо добавить некоторыеполиморфизм, чтобы сделать его более общим, чтобы использовать его с вложенными списками. Так как Haskell реализует полиморфизм с классами типов, это хорошее направление, чтобы попробовать.
Как это происходит, с этой проблемой, если вы знаете тип LHSВы можете однозначно определить как RHS, так и результат. Когда вход имеет тип [a->b]
, RHS должен быть [a]
, а результат должен быть [[b]]
. Это можно упростить до ввода a->b
, RHS[a]
, и результат [b]
. Поскольку LHS определяет другие параметры и результат, мы можем использовать fundeps или семейства типов для представления других типов.
{-# LANGUAGE TypeFamilies, UndecidableInstances #-}
class Apply inp where
type Left inp :: *
type Result inp :: *
(@@) :: inp -> [Left inp] -> [Result inp]
Теперь, когда у нас естьТип класса, мы можем сделать очевидный экземпляр для функции:
instance Apply (a -> b) where
type Left (a -> b) = a
type Result (a -> b) = b
(@@) = map
Экземпляр списка тоже не так уж плох:
instance Apply f => Apply [f] where
type Left [f] = Left f
type Result [f] = [Result f]
l @@ r = map (\f -> f @@ r) l
-- or map (@@ r) l
Теперь наш метод класса @@
долженork с произвольно глубокими списками.Вот несколько тестов:
*Main> (+) @@ [1..3] @@ [10..13]
[[11,12,13,14],[12,13,14,15],[13,14,15,16]]'s
let foo :: Int -> Char -> Char -> String
foo a b c = b:c:show a
*Main> foo @@ [1,2] @@ "ab" @@ "de"
[[["ad1","ae1"],["bd1","be1"]],[["ad2","ae2"],["bd2","be2"]]]
Возможно, вы захотите взглянуть на реализацию printf
для дальнейшего вдохновения.
Редактировать: Вскоре после публикации я понял, что можно обобщить контейнервведите мой Apply
класс от List
до Applicative
, затем используйте аппликативный экземпляр вместо map.Это позволило бы использовать как обычный список, так и поведение ZipList
.