Как определить сигнатуры функций частично в Haskell? - программирование
Подтвердить что ты не робот

Как определить сигнатуры функций частично в Haskell?

Начальная точка:

fn :: [a] -> Int
fn = (2 *) . length

Предположим, что мы хотим ограничить возвращаемое значение, тогда мы могли бы написать:

fn list = (2 * length list) :: Int

Как насчет ограничения только аргумента? Легко.

fn list = 2 * length (list :: [Char])

Пока это работает, было бы предпочтительнее иметь подписи в верхней части собраны и не рассеяны вокруг тела функции.

Это самое близкое, что я могу придумать:

fnSig = undefined :: [Char] -> a
fn | False = fnSig
   | True  = (* 2) . length

На основе http://okmij.org/ftp/Haskell/partial-signatures.lhs через http://okmij.org/ftp/Haskell/types.html#partial-sigs

Тем не менее, я хотел бы получить более чистое решение. Что-то, что сообщает, что мое намерение является частичным ограничением. Что-то вроде этого, например:

fn :: [Char] -> a
fn = (2 *) . length

Или, может быть:

fn :: [Char] -> _
fn = (2 *) . length

Возможно ли это?

Изменить для дальнейшего уточнения:

@GaneshSittampalam Сделал важный момент в комментарии ниже. Я ищу "дом на полпути между подписью типа и вообще должен дать точный". Итак, я не ищу ответ на основе TypeClass, я просто хочу, чтобы GHC заполнил пробелы для неопределенных (или не полностью ограниченных) типов моей функции.

Изменить в ответ на @WillNess

Да, что-то вроде этого...

fn list = 2 * length list
  where
    _ = list :: [Char]

... может работать, но только для аргументов, и только если функция не является точечной. Есть ли способ применить этот метод к бесцентровым функциям или возвращать значения?

Изменить в ответ на @Rhymoid

Я получил вдохновение и поиграл с идеей @Rhymoid и придумал это:

fn = (2 *) . length
  where
    _ = fn `asTypeOf` (undefined :: [Char] -> a)
    _ = fn `asTypeOf` (undefined :: a -> Int)
    _ = fn `asTypeOf` (undefined :: a -> b)
    _ = fn `asTypeOf` (undefined :: a)

Этот подход также ограничивает подпись типа fn и не загрязняет какое-либо пространство имен.

Обычно у нас будет только одна из строк asTypeOf, я просто добавил несколько, чтобы продемонстрировать, насколько мощный этот подход.

Это немного более неуклюжий, чем то, как я бы хотел, но я думаю, что это довольно аккуратно, мы можем сделать это даже без специальной синтаксической поддержки с языка.

@Rhymoid, если вам это нравится, добавьте его в свой ответ.:)

4b9b3361

Ответ 1

Извините за саморекламу, но именно эта функция является темой недавней работы доктора философии. студент Томас Винант, я, Фрэнк Писенс и Том Шрайверс, очень недавно представленный Томасом на симпозиуме PADL 2014. Подробнее см. здесь. Это функция, которая уже присутствует на некоторых других языках, но взаимодействие с такими функциями, как Haskell GADT, сделало его достаточно интересным для разработки деталей.

Томас работает над реализацией GHC. Это улучшилось с момента написания статьи, но реализация "ограничения подстановочных знаков" в GHC технически немного сложнее, чем мы ожидали. Мы ожидаем, что сможем работать дальше и связаться с разработчиками GHC, чтобы получить его, но может ли это произойти, зависит от того, сколько людей хотели бы иметь эту функцию в Haskell...

Обновление 14-4-2015: После много работы Томаса и материалов от SPJ и других людей GHC в GHC 7.10 были выпущены частичные подписи. Томас Винант написал вступительное сообщение в блоге о том, как вы можете их использовать.

Ответ 2

Я искал способ сказать 'x тип унифицировать с помощью T'. Решения, данные Will Ness и chi, близки к тому, что я придумал, но есть способ сделать это в Haskell 98, не разбирая вашу собственную функцию.

-- Your function, without type signature.
fn = (2 *) . length

-- The type signature, without actual definition.
fnTy :: [Char] -> a
fnTy = undefined

-- If this type checks, then the type of 'fn' can be unified 
--                                      with the type of 'fnTy'.
fn_unifies_with_type :: ()
fn_unifies_with_type = let _ = fn `asTypeOf` fnTy in ()

Вы даже можете пойти просто

fn = (2 *) . length
  where
    _ = fn `asTypeOf` (undefined :: [Char] -> a)

Ответ 3

Вы ищете функцию, которую хотели бы многим из нас, но у Haskell ее нет. Ничего. Вам нужны некие частичные подписи. Предложенные обозначения для этого

fn :: [Char] -> _
fn = (2*) . length

Где _ означает "здесь есть тип, но я не могу его написать".

Похоже, что очень простая функция для реализации (экземпляр _ с переменными унификации в сигнатуре), но никто не удосужился разработать семантические детали и взаимодействие с другими функциями.

Ответ 4

Чтобы указать только тип аргумента, вы можете написать что-то вроде

fn list = 2 * length list
  where
    a :: [Char]
    a = list `asTypeOf` a

Так что легко изменить его позже, например,

fn list = 2 * fromIntegral (length list)
  where
    a :: [Char]
    a = list `asTypeOf` a

и соответствующим образом измените его тип:

*Main> :t fn
fn :: [Char] -> Int
*Main> :r
-- changed file reloaded
*Main> :t fn
fn :: (Num t) => [Char] -> t

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

fn2 list = r
  where
    r :: Int
    r = f list
    f = (2 *) . length

Он также не сильно отличается от того, что у вас есть прямо сейчас, просто сохраняет код и спецификацию типа.

Ответ 5

Если тип вашего fn может быть автоматически выведен без подписи, и вы просто хотите, чтобы компилятор проверял, имеет ли предполагаемый тип правильную форму, вы можете использовать что-то следующее.

Идея состоит в том, чтобы написать что-то вроде

fnSig :: exists _1 _2. forall a. _1 a -> _2
fnSig = fn

за исключением того, что Haskell не допускает экзистенциальных типов выше. Однако экзистенциальные типы можно эмулировать с использованием типов более высокого ранга следующим образом:

{-# LANGUAGE RankNTypes #-}
fnSig :: (forall _1 _2.
            (forall a. _1 a -> _2)   -- your actual type, _ are the unknowns
          ->r)->r
fnSig = \k->k fn                     -- the compiler infers _1=[] , _2=Int

-- fn :: [] a -> Int
fn = (2 *) . length

Вышеупомянутый "трюк" по существу такой же, как тот, который используется, например. runST.

В качестве альтернативы можно объявить специальный экзистенциальный тип данных.

{-# LANGUAGE GADTs #-}
data Ex where Ex :: (forall a. _1 a -> _2) -> Ex
fnSig = Ex fn

который должен заставить компилятор выполнить проверку того же типа.

Ответ 6

Я думаю, вы хотите очень плохо. Функция, которую вы хотите, немного увеличивает удобство вывода типа, особенно для функций верхнего уровня. Но подписи деклараций верхнего уровня представляют собой существенные проектные контракты. Они API, это документация, они являются маяками для незнакомцев, которые входят в ваш код, поэтому они должны быть прочными и ясными.

Да, haskell допускает ограничения типа для возвращаемых типов. Но это в основном для временных результатов в let-blocks. И да, вы можете использовать

 f (x :: Int) = 2*x

с расширением XScopedTypeVariables (но он не применим к функциям без точек).