Подтвердить что ты не робот

Навыки уровня с литералами и инъекционным преемником? (N-ary compose)

Я обобщаю этот n -ary дополнение к составу n -ary, но у меня проблемы с приятным интерфейсом. А именно, я не могу понять, как использовать числовые литералы на уровне уровня, все еще имея возможность сопоставлять шаблоны с преемниками.

Роллинг моих собственных nats

Используя мои собственные наматывания, я могу сделать работу n -ary, но я могу передать только n как повторный итератор, а не как литерал:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}

module RollMyOwnNats where

import Data.List (genericIndex)

-- import Data.Proxy
data Proxy (n::Nat) = Proxy

----------------------------------------------------------------
-- Stuff that works.

data Nat = Z | S Nat

class Compose (n::Nat) b b' t t' where
  compose :: Proxy n -> (b -> b') -> t -> t'

instance Compose Z b b' b b' where
  compose _ f x = f x

instance Compose n b b' t t' => Compose (S n) b b' (a -> t) (a -> t') where
  compose _ g f x = compose (Proxy::Proxy n) g (f x)

-- Complement a binary relation.
compBinRel :: (a -> a -> Bool) -> (a -> a -> Bool)
compBinRel = compose (Proxy::Proxy (S (S Z))) not

----------------------------------------------------------------
-- Stuff that does not work.

instance Num Nat where
  fromInteger n = iterate S Z `genericIndex` n
-- I now have 'Nat' literals:
myTwo :: Nat
myTwo = 2
-- But GHC thinks my type-level nat literal is a 'GHC.TypeLits.Nat',
-- even when I say otherwise:
compBinRel' :: (a -> a -> Bool) -> (a -> a -> Bool)
compBinRel' = compose (Proxy::Proxy (2::Nat)) not
{-
    Kind mis-match
    An enclosing kind signature specified kind `Nat',
    but `2' has kind `GHC.TypeLits.Nat'
    In an expression type signature: Proxy (2 :: Nat)
    In the first argument of `compose', namely
      `(Proxy :: Proxy (2 :: Nat))'
    In the expression: compose (Proxy :: Proxy (2 :: Nat)) not
-}

Использование GHC.TypeLits.Nat

Используя GHC.TypeLits.Nat, я получаю литералы на уровне типа nat, но конструктор-преемник не может найти, и использование функции типа (1 +) не работает, потому что GHC (7.6.3) не может причина инъективности функций типа:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

module UseGHCTypeLitsNats where

import GHC.TypeLits

-- import Data.Proxy
data Proxy (t::Nat) = Proxy

----------------------------------------------------------------
-- Stuff that works.

class Compose (n::Nat) b b' t t' where
  compose :: Proxy n -> (b -> b') -> t -> t'

instance Compose 0 b b' b b' where
  compose _ f x = f x

instance (Compose n b b' t t' , sn ~ (1 + n)) => Compose sn b b' (a -> t) (a -> t') where
  compose _ g f x = compose (Proxy::Proxy n) g (f x)

----------------------------------------------------------------
-- Stuff that does not work.

-- Complement a binary relation.
compBinRel , compBinRel' :: (a -> a -> Bool) -> (a -> a -> Bool)
compBinRel = compose (Proxy::Proxy 2) not
{-
    Couldn't match type `1 + (1 + n)' with `2'
    The type variable `n' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    In the expression: compose (Proxy :: Proxy 2) not
    In an equation for `compBinRel':
        compBinRel = compose (Proxy :: Proxy 2) not
-}
{-
    No instance for (Compose n Bool Bool Bool Bool)
      arising from a use of `compose'
    The type variable `n' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    Note: there is a potential instance available:
      instance Compose 0 b b' b b'
-}
compBinRel' = compose (Proxy::Proxy (1+(1+0))) not
{-
    Couldn't match type `1 + (1 + 0)' with `1 + (1 + n)'
    NB: `+' is a type function, and may not be injective
    The type variable `n' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    Expected type: Proxy (1 + (1 + 0))
      Actual type: Proxy (1 + (1 + n))
    In the first argument of `compose', namely
      `(Proxy :: Proxy (1 + (1 + 0)))'
-}

Я согласен, что комбинаторы семантического редактора более изящны и более общие здесь - и конкретно, всегда будет достаточно легко написать (.) . (.) . ... (n раз) вместо compose (Proxy::Proxy n) - но я расстроен тем, что не могу сделать композицию состава n, как я и ожидал. Кроме того, похоже, я столкнулся с аналогичными проблемами для других применений GHC.TypeLits.Nat, например. при попытке определить функцию типа:

type family   T (n::Nat) :: *
type instance T 0     = ...
type instance T (S n) = ...

ОБНОВЛЕНИЕ: Сводка и адаптация принятого ответа

В принятом ответе много интересного, но ключ для меня - трюк шаблона Haskell в GHC 7.6 Решение: это эффективно позволяет мне добавлять литералы уровня уровня к моему GHC 7.6.3, у которой уже были инъекционные преемники.

Используя мои типы выше, я определяю литералы через TH:

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DataKinds #-}

module RollMyOwnLiterals where

import Language.Haskell.TH

data Nat = Z | S Nat

nat :: Integer -> Q Type
nat 0 = [t| Z |]
nat n = [t| S $(nat (n-1)) |]

где я переместил объявление Nat в новый модуль, чтобы избежать цикл импорта. Затем я изменяю свой модуль RollMyOwnNats:

+import RollMyOwnLiterals
...
-data Nat = Z | S Nat
...
+compBinRel'' :: (a -> a -> Bool) -> (a -> a -> Bool)
+compBinRel'' = compose (Proxy::Proxy $(nat 2)) not
4b9b3361

Ответ 1

РЕДАКТИРОВАТЬ: Переписан ответ. Он становился немного громоздким (и немного багги).

GHC 7.6

Поскольку тип уровня Nat несколько... неполный (?) в GHC 7.6, наименьший многословный способ достижения того, что вы хотите, представляет собой комбинацию GADT и семейств типов.

{-# LANGUAGE GADTs, TypeFamilies #-}

module Nats where

-- Type level nats
data Zero
data Succ n

-- Value level nats
data N n f g where
    Z :: N Zero (a -> b) a
    S :: N n f g -> N (Succ n) f (a -> g)

type family Compose n f g
type instance Compose Zero (a -> b) a = b
type instance Compose (Succ n) f (a -> g) = a -> Compose n f g

compose :: N n f g -> f -> g -> Compose n f g
compose Z f x = f x
compose (S n) f g = compose n f . g

Преимущество этой конкретной реализации состоит в том, что она не использует классы типов, поэтому приложения compose не подпадают под ограничение мономорфизма. Например, compBinRel = compose (S (S Z)) not будет вводить проверку без аннотаций типа.

Мы можем сделать это лучше с небольшим шаблоном Haskell:

{-# LANGUAGE TemplateHaskell #-}

module Nats.TH where

import Language.Haskell.TH

nat :: Integer -> Q Exp
nat 0 = conE 'Z
nat n = appE (conE 'S) (nat (n - 1))

Теперь мы можем написать compBinRel = compose $(nat 2) not, что гораздо более приятно для больших чисел. Некоторые могут подумать об этом "обмане", но, видя, что мы просто внедряем небольшой синтаксический сахар, я думаю, что все в порядке:)

GHC 7.8

Следующие работы в GHC 7.8:

-- A lot more extensions.
{-# LANGUAGE DataKinds, FlexibleContexts, FlexibleInstances, GADTs, MultiParamTypeClasses, PolyKinds, TypeFamilies, TypeOperators, UndecidableInstances #-}

module Nats where

import GHC.TypeLits

data N = Z | S N

data P n = P

type family Index n where
    Index 0 = Z
    Index n = S (Index (n - 1))

-- Compose is defined using Z/S instead of 0, 1, ... in order to avoid overlapping.
class Compose n f r where
    type Return n f r
    type Replace n f r
    compose' :: P n -> (Return n f r -> r) -> f -> Replace n f r

instance Compose Z a b where
    type Return Z a b = a
    type Replace Z a b = b
    compose' _ f x = f x

instance Compose n f r => Compose (S n) (a -> f) r where
    type Return (S n) (a -> f) r = Return n f r
    type Replace (S n) (a -> f) r = a -> Replace n f r
    compose' x f g = compose' (prev x) f . g
      where
        prev :: P (S n) -> P n
        prev P = P

compose :: Compose (Index n) f r => P n -> (Return (Index n) f r -> r) -> f -> Replace (Index n) f r
compose x = compose' (convert x)
  where
    convert :: P n -> P (Index n)
    convert P = P

-- This does not type check without a signature due to the monomorphism restriction.
compBinRel :: (a -> a -> Bool) -> (a -> a -> Bool)
compBinRel = compose (P::P 2) not

-- This is an example where we compose over higher order functions.
-- Think of it as composing (a -> (b -> c)) and ((b -> c) -> c).
-- This will not typecheck without signatures, despite the fact that it has arguments.
-- However, it will if we use the first solution.
appSnd :: b -> (a -> b -> c) -> a -> c
appSnd x f = compose (P::P 1) ($ x) f

Однако эта реализация имеет несколько недостатков, как указано в источнике.

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

-- Given r and f, where f = x1 -> x2 -> ... -> xN -> r, Infer r f returns N.
type family Infer r f where
    Infer r r = Zero
    Infer r (a -> f) = Succ (Infer r f)

Однако Infer не будет работать для функций более высокого порядка с полиморфными аргументами. Например:

ghci> :kind! forall a b. Infer a (b -> a)
forall a b. Infer a (b -> a) :: *
= forall a b. Infer a (b -> a)

GHC не может расширять Infer a (b -> a), потому что он не выполняет проверку при сопоставлении закрытых семейных экземпляров. GHC не будет соответствовать второму случаю Infer во избежании того, что a и b создаются так, что a объединяется с b -> a.

Ответ 2

К сожалению, на ваш вопрос не может быть дан ответ в принципе в выпущенной в настоящее время версии GHC (GHC 7.6.3) из-за проблемы согласованности, указанной в недавнем сообщении http://www.haskell.org/pipermail/haskell-cafe/2013-December/111942.html

Несмотря на то, что цифры типа типа выглядят как цифры, они не гарантируют, что они вообще будут вести себя как цифры (и они не имеют). Я видел, что Диаверки и коллеги выполнили правильную арифметику уровня в GHC (которая так же звучит, как и решатель SMT, используемый в качестве задней части, то есть мы можем доверять ей). Пока эта версия не будет выпущена, лучше избегать числовых литералов уровня, как бы они ни выглядели.