Pergunta

Vamos considerar um tipo de dados com muitos construtores:

data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int

Quero escrever uma função para verificar se dois valores são produzidos com o mesmo construtor:

sameK (Alpha _) (Alpha _) = True
sameK (Beta _) (Beta _) = True
sameK (Gamma _ _) (Gamma _ _) = True
sameK _ _ = False

Manutenção sameK Não é muito divertido, não pode ser verificado para correção facilmente. Por exemplo, quando novos construtores são adicionados a T, é fácil esquecer de atualizar sameK. Eu omiti uma linha para dar um exemplo:

-- it’s easy to forget:
-- sameK (Delta _) (Delta _) = True

A questão é como evitar o boilerplate em sameK? Ou como garantir que ele verifique para todos T construtores?


A solução alternativa que encontrei é usar tipos de dados separados para cada um dos construtores, derivando Data.Typeable, e declarando uma classe de tipo comum, mas não gosto dessa solução, porque é muito menos legível e, de outra forma

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Typeable

class Tlike t where
  value :: t -> t
  value = id

data Alpha = Alpha Int deriving Typeable
data Beta = Beta Int deriving Typeable
data Gamma = Gamma Int Int deriving Typeable
data Delta = Delta Int deriving Typeable

instance Tlike Alpha
instance Tlike Beta
instance Tlike Gamma
instance Tlike Delta

sameK :: (Tlike t, Typeable t, Tlike t', Typeable t') => t -> t' -> Bool
sameK a b = typeOf a == typeOf b
Foi útil?

Solução

Veja o módulo Data.data, o toConstr função em particular. Juntamente com {-# LANGUAGE DeriveDataTypeable #-} Isso lhe dará uma solução de 1 linha que funciona para qualquer tipo que seja uma instância de Data.Data. Você não precisa descobrir todo o Syb!

Se, por algum motivo (preso com abraços?), Isso não é uma opção, aqui está um hack muito feio e muito lento. Funciona apenas se o seu tipo de dados for Showcapaz (por exemplo, usando deriving (Show) - o que significa nenhum tipo de função dentro, por exemplo).

constrT :: T -> String
constrT = head . words . show
sameK x y = constrT x == constrT y

constrT recebe a representação da string do construtor mais externo de um T valor mostrando -o, cortando -o em palavras e depois recebendo o primeiro. Eu dou uma assinatura de tipo explícita para que você não seja tentado a usá -la em outros tipos (e a fugir da restrição de monomorfismo).

Algumas desvantagens notáveis:

  • Isso quebra terrivelmente quando seu tipo tem construtores de infix (como data T2 = Eta Int | T2 :^: T2)
  • Se alguns de seus construtores tiverem um prefixo compartilhado, isso será mais lento, pois uma parte maior das cordas deve ser comparada.
  • Não funciona em tipos com um costume show, como muitos tipos de biblioteca.

Dito isto, isso é Haskell 98 ... mas isso é a única coisa boa que posso dizer sobre isso!

Outras dicas

Outra maneira possível:

sameK x y = f x == f y
  where f (Alpha _)   = 0
        f (Beta _)    = 1
        f (Gamma _ _) = 2
        -- runtime error when Delta value encountered

Um erro de tempo de execução não é ideal, mas melhor do que dar a resposta errada silenciosamente.

Você precisará usar uma biblioteca de genéricos, como sucata o seu caldeira ou uniplate para fazer isso em geral.

Se você não quer ser tão pesado, pode usar a solução de Dave Hinton, juntamente com o atalho de registro vazio:

...
where f (Alpha {}) = 0
      f (Beta {}) = 1
      f (Gamma {}) = 2

Então você não precisa saber quantos args cada construtor possui. Mas obviamente ainda deixa algo a desejar.

Em alguns casos, a biblioteca "raspa sua caldeira" ajudará.

http://www.haskell.org/haskellwiki/scrap_your_boilerplate

Definitivamente, você pode usar os genéricos para eliminar o caldeira. Seu código é um exemplo de livro didático por que eu (e muitos outros Nunca use o _ curinga no nível superior). Embora seja tedioso escrever todos os casos, é menos tedioso do que lidar com os bugs.

Neste exemplo feliz, eu não apenas usaria a solução de Dave Hinton, mas também daria um tapa em um Pragma em linha na função auxiliar f.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top