Предположим, что у меня есть функция со следующей подписью типа:
g :: a -> a -> a -> b
У меня также есть список a
s — давайте назовем его xs
— то, что я знаю, будет содержать по крайней мере три объекта. Я хотел бы подать заявку g
к первым трем объектам xs
. Я знаю, что мог определить combinator как следующее:
($$$) :: (a -> a -> a -> b) -> [a] -> b
f $$$ (x:y:z:_) = f x y z
Затем я мог просто использовать g $$$ xs
. Это делает $$$
немного как uncurry
, но для функции с тремя аргументами того же типа и списка вместо кортежа.
Существует ли способ сделать этот идиоматически стандарт использования combinators? Или скорее что самый идиоматический путь состоит в том, чтобы сделать это в Haskell? Я думал, пробуя pointfree
на неинфиксной версии $$$
мог бы дать мне некоторое представление о том, где запустить, но вывод был отвращением с 10 flip
s, горстка head
s и tail
s и ap
s, и 28 круглых скобок.
(NB: я знаю это, не ужасно вещь Haskelly сделать во-первых, но я столкнулся с несколькими ситуациями, где это походит на разумное решение, особенно при использовании Парсека. Я, конечно, приму, "никогда не делают этого в реальном коде", если бы это - лучший ответ, но я предпочел бы видеть, что некоторый умный прием включает ((->) r)
монада или что бы то ни было.)
Или, скорее, какой наиболее идиоматичный способ сделать это в Haskell?
Идиоматичный? Если вам действительно нужна функция, которая делает то, что делает ($$$)
, то код, который у вас есть, вероятно, настолько идиоматичен, насколько это возможно.
Я бы предпочел увидеть какой-нибудь хитрый трюк
О, ну, в том случае.
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE UndecidableInstances #-}
class ListApply f a r | f -> a r where
($...) :: f -> [a] -> r
instance (TypeCast b r) => ListApply b a r where
x $... _ = typeCast x
instance (ListApply f a r) => ListApply (a -> f) a r where
f $... (x:xs) = (f x) $... xs
Вот оно, полностью общее решение: Дана функция произвольной четности с сигнатурой типа a -> a ... -> b
, примените ее к стольким элементам списка [a]
, сколько необходимо. Демонстрация:
ones :: [Int]
ones = repeat 1
test1 x = x
test2 x y = x + y
test3 x y z = (x + z) * (y + z)
В GHCi:
> test1 $... ones
1
> test2 $... ones
2
> test3 $... ones
4
Я, конечно, приму "никогда не делайте этого в реальном коде", если это лучший ответ
Вы, вероятно, захотите пойти с этим.
О, и немного шаблонов, необходимых для выполнения вышеприведенного кода:
class TypeCast a b | a -> b, b->a where typeCast :: a -> b
class TypeCast' t a b | t a -> b, t b -> a where typeCast' :: t->a->b
class TypeCast'' t a b | t a -> b, t b -> a where typeCast'' :: t->a->b
instance TypeCast' () a b => TypeCast a b where typeCast x = typeCast' () x
instance TypeCast'' t a b => TypeCast' t a b where typeCast' = typeCast''
instance TypeCast'' () a a where typeCast'' _ x = x
Это швейцарский армейский нож метапрограммирования на уровне типов, любезно предоставленный Олегом Киселевым.
f $$$ (x:y:z:_) = f x y z
На мой взгляд, это наиболее идиоматичный и лаконичный способ. Если количество аргументов меняется, вы можете использовать Template Haskell или делать это итеративно - определить:
zero = const
next n f (x:xs) = n (f x) xs
, тогда ваша функция будет next (next (next zero)))
, и это работает для любого вложения следующий
.
Также вы можете разбить его на более примитивные комбинаторы:
firstThree (x:y:z:_) = (x,y,z)
uncurry3 f (x,y,z) = f x y z
g f = uncurry3 f . firstThree