Haskell: Функция для определения арности функций?

Можно ли написать функцию arity :: a -> Integer для определения арности произвольных функций, такую, что

> arity map
2
> arity foldr
3
> arity id
1
> arity "hello"
0

?


person Frank S. Thomas    schedule 03.12.2011    source источник
comment
Я считаю, что можно использовать хитрые трюки с системой типов. Поиск вариативных или поливариантных функций в haskell.   -  person scravy    schedule 03.12.2011
comment
Я думаю, что это интересный вопрос, и я поражен ответом Макса Талдыкина, но мне интересно - для чего бы вы использовали такую ​​функцию?   -  person Frerich Raabe    schedule 21.09.2012
comment
@Frerich Во время этого вопроса я читал Элементы программирования Степанова и МакДжонса, где они представили тип атрибут Arity(F), который возвращает количество входов F. Мне было любопытно, смогу ли я реализовать некоторые из функций, которые они определили в Haskell.   -  person Frank S. Thomas    schedule 23.09.2012


Ответы (6)


С OverlappingInstances это просто:

{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}

class Arity f where
  arity :: f -> Int

instance Arity x where
  arity _ = 0

instance Arity f => Arity ((->) a f) where
  arity f = 1 + arity (f undefined) 

Upd Обнаружена проблема. Вам нужно указать неполиморфный тип для полиморфных функций:

arity (foldr :: (a -> Int -> Int) -> Int -> [a] -> Int)

Пока не знаю, как это решить.

Upd2, как прокомментировал Sjoerd Visscher ниже, «вы должны указать неполиморфный тип, поскольку ответ зависит от того, какой тип вы выберете».

person max taldykin    schedule 03.12.2011
comment
Зачем нужны OverlappingInstances? - person scravy; 03.12.2011
comment
@scravy, instance Arity x более общий, чем instance Arity ((->) a f). Таким образом, без расширений GHC не может выбрать, какой из этих двух экземпляров использовать для функций. OverlappingInstances указывает GHC, что а) такие случаи разрешены; б) ей нужно выбрать наиболее конкретный. - person max taldykin; 03.12.2011
comment
@max У меня не работает:arity const дает мне Ambiguous type variable 'a0' in the constraint: (Arity a0) arising from a use of 'arity' - person Frank S. Thomas; 03.12.2011
comment
Имеет смысл указать неполиморфный тип, поскольку ответ зависит от того, какой тип вы выберете, например: arity (foldr :: (a -> (Int -> Int) -> Int -> Int) -> (Int -> Int) -> [a] -> Int -> Int) - person Sjoerd Visscher; 03.12.2011
comment
Хорошо, немного поигравшись, решение состоит в том, чтобы добавить прагму IncoherentInstances LANGUAGE;) - person is7s; 03.12.2011
comment
@is7s - приятно знать, что для Incoherent Instances есть реальное применение :) И +1 за этот классный ответ, здорово видеть, как arity foldr производит 3 в ghci. - person Dan Burton; 04.12.2011
comment
@is7s - buuuut IncoherentInstances путается с лямбда-выражениями. arity $ \x y -> 3 производит 0 с непоследовательными экземплярами, 2 без них. Возможно, это та область, где IncoherentInstances можно было бы улучшить? - person Dan Burton; 04.12.2011
comment
@DanBurton Я не совсем уверен, что здесь происходит, но я думаю, что лямбда-выражения имеют другое внутреннее представление, чем обычные функции, которые вызывают это. Их можно как-то оптимизировать. Например, arity (\x y z -> (x,y,z)) is 3, арность (\x y z -> x)` равно 0, arity (\x y z -> (x,y) равно 2, а arity (\x y z -> (x,z)) неожиданно равно 1. Здесь следует отметить, что если все переменные в левой части встречаются в правой части, то результат правильный, однако, если не все они встречаются в правой части, результат не имеет смысла. Нам нужен эксперт по GHC :D - person is7s; 04.12.2011
comment
Путаница для лямбда-выражений не требуетIncoherentInstances. По умолчанию путается. Например, arity (\x -> x) дает 0. На самом деле это происходит всякий раз, когда лямбда не делает что-то полезное для параметра. Если вы делаете что-то полезное, например arity (\x -> x + 1), вы получаете 1. Если вы делаете что-то полезное с двумя параметрами (например, суммируете их), вы получаете 2. Я думаю, что это как-то связано с ленью, если параметр не используется в полезный способ, он не считает это арностью. - person CMCDragonkai; 21.08.2015
comment
@CMCDragonkai Похоже, в этом виновата оптимизация компилятора ???? - person jsdw; 23.08.2015

Да, это можно сделать очень и очень легко:

arity :: (a -> b) -> Int
arity = const 1

Обоснование: если это функция, вы можете применить ее ровно к 1 аргументу. Обратите внимание, что синтаксис haskell делает невозможным применение к 0, 2 или более аргументам, поскольку f a b на самом деле является (f a) b, то есть не f applied to a and b, а (f applied to a) applied to b. Результатом, конечно, может быть другая функция, которую можно применить снова, и так далее.

Звучит глупо, но это не что иное, как правда.

person Ingo    schedule 03.12.2011
comment
+1 за глупую правду. Все функции Haskell имеют арность 1, потому что функции могут производить функции. a -> b -> c это просто сахар для a -> (b -> c). - person Dan Burton; 04.12.2011
comment
Итак, можно ли рекурсивно найти глубину дерева функций? - person John F. Miller; 05.12.2011

Если id имеет арность 1, разве id x не должна иметь арность 0? Но, например, id map идентично map, у которого в вашем примере будет арность 2.

Имеют ли следующие функции одинаковую арность?

f1 = (+)
f2 = (\x y -> x + y)
f3 x y = x + y

Я думаю, что ваше понятие "арность" не совсем точно определено...

person sth    schedule 03.12.2011
comment
Разве вы не можете просто сказать, что id x имеет арность x? Я имею в виду, это разумно, если вы посмотрите на id :: a -> a. - person Tarrasch; 03.12.2011
comment
Мое определение арности: подсчитайте количество -> в типе функции, которые не заключены в круглые скобки. Таким образом, арность id x зависит от x. - person Frank S. Thomas; 03.12.2011
comment
Я определяю арность как количество раз, когда вы можете применить аргумент к термину. Если тип аргумента не указан, используйте (). - person fuz; 03.12.2011

В Haskell каждая «функция» принимает ровно один аргумент. То, что выглядит как «многоаргументная» функция, на самом деле является функцией, которая принимает один аргумент и возвращает другую функцию, которая принимает остальные аргументы. Так что в этом смысле все функции имеют арность 1.

person newacct    schedule 03.12.2011

Это невозможно со стандартным Haskell. Это может быть возможно с использованием IncoherentInstances или аналогичного расширения.

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

Какую настоящую проблему вы пытаетесь решить?

person dave4420    schedule 03.12.2011
comment
На самом деле проблемы нет, мне просто интересно. - person Frank S. Thomas; 03.12.2011
comment
Я нашел вариант использования, я хочу поднять обычные функции в эквивалентные сопрограммы enumeratee или tranducer. Было бы полезно знать, сколько параметров может принимать функция, пока она не вернет экземпляр типа вида *. - person CMCDragonkai; 21.08.2015

Как насчет этого:

arity :: a -> Int
arity (b->c) = 1 + arity (c)
arity _ = 0
person John F. Miller    schedule 05.12.2011
comment
GHCI жалуется: Не входит в объем: b - person Frank S. Thomas; 06.12.2011