Использование элементов в списке в качестве аргументов - PullRequest
10 голосов
/ 24 мая 2010

Предположим, у меня есть функция со следующей сигнатурой типа:

g :: a -> a -> a -> b

У меня также есть список a с, назовем его xs, который, как я знаю, будет содержать как минимум три элемента. Я хотел бы применить g к первым трем пунктам xs. Я знаю, что мог бы определить комбинатор, как показано ниже:

($$$) :: (a -> a -> a -> b) -> [a] -> b
f $$$ (x:y:z:_) = f x y z

Тогда я мог бы просто использовать g $$$ xs. Это делает $$$ немного похожим на uncurry, но для функции с тремя аргументами одного типа и списком вместо кортежа.

Есть ли способ сделать это идиоматически, используя стандартные комбинаторы? Или, скорее, какой самый идиоматичный способ сделать это в Haskell? Я думал, что попытка pointfree на неинфиксной версии $$$ может дать мне некоторое представление о том, с чего начать, но на выходе получилось мерзко с 10 flip с, несколькими head с и tail s и ap s и 28 скобок.

(NB: Я знаю, что это не очень ужасная вещь, которую нужно делать Хаскели, во-первых, но я сталкивался с несколькими ситуациями, когда это кажется разумным решением, особенно при использовании Parsec. примите «не когда-либо делайте это в реальном коде», если это лучший ответ, но я бы предпочел увидеть какой-нибудь хитрый трюк с монадой ((->) r) или чем-то еще.

Ответы [ 2 ]

12 голосов
/ 25 мая 2010

Или, скорее, какой самый идиоматичный способ сделать это в 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

Это швейцарский армейский нож метапрограммирования на уровне типов, любезно предоставленный Олег Киселев .

7 голосов
/ 25 мая 2010
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))), и это работает для любой вложенности next.

Также вы можете разбить его на более примитивные комбинаторы:

firstThree (x:y:z:_) = (x,y,z)
uncurry3 f (x,y,z) = f x y z
g f = uncurry3 f . firstThree
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...