Как составить `не` с функцией произвольной arity?
Когда у меня есть функция типа типа
f :: (Ord a) => a -> a -> Bool
f a b = a > b
Мне бы хотелось сделать функцию, которая не переносит эту функцию.
например. выполните функцию, подобную этой
g :: (Ord a) => a -> a -> Bool
g a b = not $ f a b
Я могу сделать комбинатор вроде
n f = (\a -> \b -> not $ f a b)
Но я не знаю, как.
*Main> let n f = (\a -> \b -> not $ f a b)
n :: (t -> t1 -> Bool) -> t -> t1 -> Bool
Main> :t n f
n f :: (Ord t) => t -> t -> Bool
*Main> let g = n f
g :: () -> () -> Bool
Что я делаю неправильно?
И бонусный вопрос, как я могу это сделать для функции с большим количеством параметров и, кроме того,
t -> Bool
t -> t1 -> Bool
t -> t1 -> t2 -> Bool
t -> t1 -> t2 -> t3 -> Bool
Ответы
Ответ 1
Если вы не хотите взломать стили, которые лучше оставить для мысленных экспериментов и доказательств концепции, вы просто не обобщаете несколько аргументов. Не пытайтесь.
Что касается вашего основного вопроса, это наиболее элегантно решается с помощью комбинаторов семантического редактора Conal Elliott. Комбинатор семантического редактора - это функция с типом типа:
(a -> b) -> F(a) -> F(b)
Где F (x) - некоторое выражение, включающее x. Существуют также "контравариантные" комбинаторы редакторов, которые вместо этого принимают (b -> a)
. Интуитивно, комбинатор редакторов выбирает часть некоторого большего значения для работы. Тот, который вам нужен, называется result
:
result = (.)
Посмотрите на тип выражения, которое вы пытаетесь использовать:
a -> a -> Bool
Результат (codomain) этого типа является a → Bool, а результатом этого типа является Bool, и это то, что вы пытаетесь применить not
to. Поэтому, чтобы применить not
к результату результата функции f
, вы пишете:
(result.result) not f
Это прекрасно обобщает. Вот еще несколько комбинаторов:
argument = flip (.) -- contravariant
first f (a,b) = (f a, b)
second f (a,b) = (a, f b)
left f (Left x) = Left (f x)
left f (Right x) = Right x
...
Итак, если у вас есть значение x
типа:
Int -> Either (String -> (Int, Bool)) [Int]
И вы хотите применить not
к Bool, вы просто указали путь, чтобы попасть туда:
(result.left.result.second) not x
О, и если вы добрались до Functors, вы заметите, что fmap
является комбинатором редакторов. Фактически, вышесказанное может быть написано:
(fmap.left.fmap.fmap) not x
Но я думаю, что яснее использовать расширенные имена.
Enjoy.
Ответ 2
На самом деле выполнение произвольной сущности с типами классов оказывается невероятно легким:
module Pred where
class Predicate a where
complement :: a -> a
instance Predicate Bool where
complement = not
instance (Predicate b) => Predicate (a -> b) where
complement f = \a -> complement (f a)
-- if you want to be mysterious, then
-- complement = (complement .)
-- also works
ge :: Ord a => a -> a -> Bool
ge = complement (<)
Спасибо, что указали эту крутую проблему. Я люблю Хаскелла.
Ответ 3
Ваш n комбинатор можно записать:
n = ((not .) .)
Что касается вашего бонусного вопроса, типичным способом было бы создать несколько из них:
lift2 = (.).(.)
lift3 = (.).(.).(.)
lift4 = (.).(.).(.).(.)
lift5 = (.).(.).(.).(.).(.)
и др.
Ответ 4
Re: Что я делаю неправильно?:
Я думаю, что ваш комбинатор в порядке, но когда вы позволяете ему связывать его на верхнем уровне, один из хакерских раздражающих "правил по умолчанию" вступает в игру, и привязка не обобщается:
Prelude> :ty (n f)
(n f) :: (Ord t) => t -> t -> Bool
Prelude> let g = n f
Prelude> :ty g
g :: () -> () -> Bool
Я думаю, что вы можете быть сбиты "ограничением мономорфизма", как это относится к классам типов. В любом случае, если вы выходите из цикла верхнего уровня и помещаете вещи в отдельный файл с явной подписью типа, все работает отлично:
module X where
n f = (\a -> \b -> not $ f a b)
f a b = a > b
g :: Ord a => a -> a -> Bool
g = n f
Бонусный вопрос. Чтобы сделать это с большим количеством параметров типа, вы можете попробовать сыграть хитроумные трюки с системой типа класса. Две статьи для справки: Hughes and Claessen бумага в QuickCheck и бумага Ральфа Хинзе Общие для масс.