Общее определение арности типов данных

Я хочу рассчитать «арность» определенного класса типы данных. А именно типы данных с одним конструктором и некоторым количеством полей. Например. data T a = T Int () String a. Тогда «арность» будет количеством полей. Для T a это будет 4. Я представляю функцию с сигнатурой примерно такой:

forall a . C a => Int

для некоторого подходящего выбора C. Я знаю, что если у меня есть Generic a для некоторого типа a, я получаю from :: a -> Rep a x, но обратите внимание, что для этого потребуется конкретное значение для a, и мне интересно вычислить его статически. Это возможно как-то? Я тоже думал о Typeable, но я не очень понимаю API.

Вы можете использовать Generic на уровне типа. Вам не нужно создавать значение Rep, вы можете проверить его тип.

Fyodor Soikin 28.05.2019 22:36

Я не уверен, что следую. Как бы вы реализовали forall a . Generic a => Int?

fredefox 28.05.2019 22:44

Если вы просто пытаетесь избежать передачи конкретного значения для a, вы можете использовать значение Proxy или расширение TypeApplications. Если вы хотите, чтобы эти вычисления происходили во время компиляции, а не во время выполнения, я не удивлюсь, если GHC сделает это независимо во время оптимизации, но я не знаю наверняка.

DarthFennec 28.05.2019 23:01

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

fredefox 28.05.2019 23:04

Найти арность конструктора довольно просто.. например. называя это arity T (T на уровне значений)

luqui 28.05.2019 23:04

@luqui как ты реализуешь arity? Или, если вы имеете в виду библиотечную функцию, можете ли вы предоставить ссылку?

fredefox 28.05.2019 23:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
134
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Чтобы ответить мне на ваш вопрос в комментариях, вот пример того, как вы можете найти арность функции.

{-# LANGUAGE ScopedTypeVariables, FlexibleInstances #-}

import Data.Proxy

class Arity a where
    arityP :: Proxy a -> Int

instance {-# OVERLAPPABLE #-} Arity a where
    arityP _ = 0

instance {-# OVERLAPPING #-} Arity b => Arity (a -> b) where
    arityP f = 1 + arityP (Proxy :: Proxy b)

arity :: forall a. Arity a => a -> Int
arity _ = arityP (Proxy :: Proxy a)

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

ghci> arity T
4

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

ghci> arity id
<interactive>:2:1: error:
• Overlapping instances for Arity a0 arising from a use of ‘arity’
  Matching instances:
    instance [overlappable] [safe] Arity a -- Defined at arity.hs:10:31
    instance [overlapping] [safe] Arity b => Arity (a -> b)
      -- Defined at arity.hs:13:30

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

id :: Int -> Int
id :: (Int -> Int) -> Int -> Int

Что на самом деле увеличивает мою уверенность в этом подходе. Дайте мне знать, как это работает.

Это не совсем то, к чему я стремился. Я могу понять, если не очень ясно, к чему я стремлюсь. Во-первых, я должен был назвать конструктор значений и типов в моем примере по-другому. Ваша реализация arity все еще нуждается в передаче значения. Я надеялся, что, например. Typeable позволил бы мне ответить на вопрос, предполагая, что некоторый тип T принадлежит классу, описанному выше, сколько у него полей. В контексте, в котором я хочу использовать это, мой тип появляется только в положительной позиции. Поэтому я не могу передать его в другую функцию. Используйте только информацию на уровне типа.

fredefox 28.05.2019 23:48

@fredefox да, я отвечал на твой вопрос в комментариях. Вы можете получить то, что ищете, используя GHC.Generics.

luqui 28.05.2019 23:51

Ах да, вы действительно сказали арность конструктор.

fredefox 28.05.2019 23:52

У типа может быть много конструкторов, каждый из которых имеет разную арность. GHC не знает, что у вашего типа есть только один конструктор, если только вы не используете Generics - см. ответ Ли-яо Ся. Поэтому, если вам нужна арность конструктора, вы должны предоставить конструктор, то есть значение, а не только тип данных.

AntC 29.05.2019 02:51
Ответ принят как подходящий

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

data T = T Int Bool String deriving Generic

-- Used extension: DeriveGeneric

Производный экземпляр включает экземпляр семейства типов для Rep, который создает общее представление типа T. Rep T использует фиксированный набор типов из модуль GHC.Generics:

type Rep T = M1 D _ ((M1 C _ (K1 _ Int) :*: M1 C _ (K1 _ Bool)) :*: M1 C _ (K1 _ String))
--
-- Irrelevant details hidden in underscores.
-- There's actually a few more M1's as well
--
-- You can see the full and real details in a ghci session with this command
--   :kind! Rep T

Функция Арити

Мы определим функцию уровня типа для проверки этой структуры и вычисления количества полей. Это его подпись:

type family Arity (f :: Type -> Type) :: Nat
-- If T is a type with one constructor (C x1 ... xn),
-- Arity (Rep T) is the arity n of that constructor

-- Used extensions: TypeFamilies, DataKinds

Когда дело доходит до обобщенного представления, мы можем представить, что TT = (Type->Type) похож на АТД со следующими конструкторами:

-- We can pretend that there is this data type TT
-- such that Arity is a function (TT -> Nat)
data TT
  = M1 Type Meta TT
  | (:+:) TT TT
  | V1
  | (:*:) TT TT
  | U1
  | K1 Type Type

Очень (слишком?) краткий обзор. M1 содержит такую ​​информацию, как имена типов (включая модуль и пакет), имена конструкторов, использует ли конструктор нотацию записи, строгость полей... V1 и (:+:) используются для типов с нулем или многими конструкторами, поэтому они не относятся к нас. U1 представляет нулевые конструкторы, а (:*:) разделяет n-арные конструкторы с представлением половины полей с обеих сторон. K1 отмечает одно поле конструктора.

Мы определяем функцию Arity, давая ей экземпляры семейства типов. Но на самом деле, для первого понимания, игнорируйте ключевые слова type instance и представьте, что Arity — это функция, определяемая сопоставлением с образцом, как обычно.

Глядя на представление Rep T выше, мы сначала сталкиваемся с узлом M1, который мы игнорируем и рекурсивно вызываем Arity его содержимое.

type instance Arity (M1 i c f) = Arity f

Затем мы видим (:*:), который разбивает набор полей на две части; мы рекурсивно вычисляем их значения и суммируем.

type instance Arity (f :*: g) = Arity f + Arity g

-- Used extensions: TypeOperators, UndecidableInstances

U1 представляет нульарные конструкторы,

type instance Arity U1 = 0

а K1 — одно поле.

type instance (K1 i a) = 1

Теперь, учитывая общий тип T (т. е. с экземпляром Generic), Arity (Rep T) является его арностью как уровень типа Nat. В ghci мы можем проверить это с помощью

:kind! Arity (Rep T)

Используйте GHC.TypeNats.natVal, чтобы преобразовать его в значение Natural (например, Integer, но неотрицательное).

-- Calculate the arity of the constructor of a generic type `a`.
-- `a` must have a single constructor.
arity :: forall a. (Generic a, KnownNat (Arity (Rep a))) => Natural
arity = natVal (Proxy @(Arity (Rep a)))

-- Used extensions:
--   ScopedTypeVariables,
--   AllowAmbiguousTypes, TypeApplications,
--   FlexibleContexts

Мы получаем арность любого универсального типа T как значение arity @T, которое можно преобразовать, например, с помощью fromIntegral :: Natural -> Integer.

main = print (arity @T)

Полная суть: https://gist.github.com/Lysxia/10f1da354f051b2d2eb24f6aace1bf9c

Ух ты! Для этого потребовалось гораздо больше техники, чем я думал. Я думал, что ответ будет в Rep, как указал @Fyodor Soikin в комментариях, но, поскольку это сам по себе тип, я не мог понять, как написать такую ​​​​функцию. Я полагаю, мне следует изучить «Мышление с типами». Интересно, сделает ли -XDependentTypes решение более элегантным... Отличный ответ!

fredefox 29.05.2019 09:22

Надеюсь, вы не возражаете, что я использовал его здесь: github.com/fredefox/mysql-simple/blob/fredefox/generic-quasi‌​/… для этого PR: github.com/paul-rouse/mysql-simple/pull/50

fredefox 29.05.2019 17:00

Конечно, это совершенно бесплатно!

Li-yao Xia 29.05.2019 20:15

Другие вопросы по теме