/ / Por que não posso corresponder a um padrão em uma família de tipos? - haskell, tipos

Por que não posso combinar com um tipo de família? - haskell, tipos

Considere o seguinte código:

{-# LANGUAGE TypeFamilies #-}

data Twothings a b = Twothings a b

type family Leftthing a where
Leftthing (Twothings a b) = Leftthing a
Leftthing a = a

leftthing :: a -> Leftthing a
leftthing (Twothings a b) = leftthing a
leftthing b = b

Não compila, com o seguinte erro:

Couldn"t match expected type ‘a’
with actual type ‘Twothings a0 t0’
‘a’ is a rigid type variable bound by
the type signature for:
leftthing :: forall a. a -> Leftthing a

Ele reclama da linha leftthing (Twothings a b) = leftthing a. Se bem entendi, não é possível unificar a variável de tipo a na assinatura de tipo com o tipo do construtor Twothings. Ok, isso parece fazer sentido. Mas então, como posso definir uma função com famílias de tipos na assinatura de tipo?

Respostas:

11 para resposta № 1

Quando você declara

leftthing :: a -> Leftthing a

você está dizendo que o chamador do leftthing começa a escolher o que a é.

Quando você escreve

leftthing (Twothings a b) = leftthing a

tu es presumindo que eles escolheram um Twothings tipo, e como isso não é necessariamente o caso, seu programa é rejeitado.

Você pode ter pensado que você era testando se eles escolheram um Twothings tipo, mas não! As informações de tipo são apagadas antes do tempo de execução, portanto, não há como fazer esse teste.

Você posso tente restaurar as informações necessárias sobre o tempo de execução. Primeiro, deixe-me corrigir a inconsistência entre o seu Leftthing e leftthing.

type family Leftthing a where
Leftthing (Twothings a b) = Leftthing{-you forgot the recursion!-} a
Leftthing a = a

Agora podemos definir o GADT de testemunhas para Twothingness.

data IsItTwothings :: * -> * where
YesItIs   :: IsItTwothings a -> IsItTwothings (Twothings a b)
NoItIsn"t :: Leftthing a ~ a => IsItTwothings a
-- ^^^^^^^^^^^^^^^ this constraint will hold for any type
-- which is *definitely not* a Twothings type

E então podemos passar a testemunha como um argumento:

leftthing :: IsItTwothings a -> a -> Leftthing a
leftthing (YesItIs r) (Twothings a b) = leftthing r a
leftthing NoItIsn"t   b               = b

Com efeito, a testemunha é a codificação unária do número de aninhadas à esquerda Twothingses na raiz do seu tipo. Isso é informação suficiente para determinar em tempo de execução a quantidade correta de descompactação para fazer.

> leftthing (YesItIs (YesItIs NoItIsn"t)) (Twothings (Twothings True 11) (Twothings "strange" [42]))
True

Para resumir, você não pode descobrir um tipo por padrãocorrespondência em um valor. Em vez disso, você precisa conhecer o tipo para fazer a correspondência de padrões (porque o tipo determina o layout da memória e não há tags do tipo de tempo de execução). Você não pode combinar os tipos diretamente (porque eles simplesmente não estão lá para serem correspondidos). Você pode construir tipos de dados que agem como evidências de tempo de execução da estrutura de tipos e corresponder a eles.

Talvez, um dia, seu programa funcione se você der o tipo

leftthing :: pi a. a -> Leftthing a

Onde pi é o quantificador dependente, indicando que o argumento de tipo oculto não é apagado, mas sim passado e correspondido em tempo de execução. Esse dia ainda não chegou, mas acho que será.