2016-09-13 4 views
0

Я хочу создать рекурсивный тип экземпляра на основе кортежей. Я ищу что-то подобное:Haskell Рекурсивный тип классов

class Provider a b where 
    getInstance :: a -> b 

instance Provider a b => Provider (x, a) b where 
    getInstance (x, a) = getInstance a 

instance Provider (b, x) b where 
    getInstance (b, _) = b 

tryFunc1 :: Int 
tryFunc1 = 
    let provider = ("test", (10,())) :: (String, (Int,())) 
    in getInstance provider 

tryFunc2 :: String 
tryFunc2 = 
    let provider = ("test", (10,())) :: (String, (Int,())) 
    in getInstance provider 

К сожалению, haskell не может решить проблему. Любая причина?

+2

Предположительно, вы хотите, чтобы Haskell использовал первый экземпляр над вторым, когда это было возможно. К сожалению, при попытке решить, какой экземпляр использовать, когда есть несколько кандидатов, GHC только смотрит на голову экземпляра, чтобы попытаться определить, что является наиболее конкретным. – Alec

+0

Но в тех случаях, когда я писал, решение должно быть детерминированным, так как в tryFunc2 второй экземпляр является единственным, у которого String является аргументом второго типа, а в случае tryFunc1 - это комбинация первого и второго , Существует только одно возможное решение. –

+2

@FernandoRincon Дизайн системы типа Haskell гарантирует гораздо больше, чем просто уникальное решение: он гарантирует, что решение можно найти, не выполняя поиск (откат). Это компромисс между эффективностью и выразительностью, и, на мой взгляд, он хорошо послужил языку. Предполагаемые вами экземпляры, в общем, требуют от компилятора выполнить поиск, чтобы найти решение, даже если оно уникально - и поэтому отклоняются. –

ответ

5

Решение состоит в том, чтобы прекратить использование устаревшей прагмы OverlappingInstances и начать использовать для примера OVERLAPPING и OVERLAPPABLE прагмы. С помощью всего этого изменения:

instance {-# OVERLAPPABLE #-} Provider a b => Provider (x, a) b where 
    getInstance (x, a) = getInstance a 

instance {-# OVERLAPPING #-} Provider (b, x) b where 
    getInstance (b, _) = b 

я tryFunc1 быть 10 и tryFunc2 быть "test".


Технически вам нужно только либо в OVERLAPPABLE или OVERLAPPING прагму, но я считаю, что это хорошая практика, чтобы и в этом случае ... Кроме того, я полагаю, что это поведение, которое вы хотите, но обратите внимание, что это просто получает первый любого типа вы ищете (так getInstance (10, (20,())) :: Int дает мне 10 и не20)

Хороший источник информации является ticket отслеживания создания описываемого объекта.

0

Я знаю, что некоторые люди не любят UndecidableInstances, но это то, что мне нравится делать в этом типе ситуации: используйте закрытый type family, чтобы сделать его явным, чтобы выбор был полностью детерминированным.

Идея состоит в том, чтобы семейство типов вычислило булевский флаг, давая понять, какая ветка должна быть взята механизмом разрешения типа. Расширение UndecidableInstance необходимо из-за ограничения Provider a b (AtHead a b) =>, хотя оно безвредно.

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

module Provider where 

import Data.Proxy 
import Data.Type.Equality 

class Provider a b (f :: Bool) where 
    getInstance' :: Proxy f -> a -> b 

type family AtHead x y :: Bool where 
    AtHead (x, a) y = x == y 

instance Provider a b (AtHead a b) => Provider (x, a) b 'False where 
    getInstance' _ (x, a) = getInstance' (Proxy :: Proxy (AtHead a b)) a 

instance Provider (b, x) b 'True where 
    getInstance' _ (b, _) = b 

getInstance :: forall a b. Provider a b (AtHead a b) => a -> b 
getInstance = getInstance' (Proxy :: Proxy (AtHead a b)) 

tryFunc1 :: Int 
tryFunc1 = 
    let provider = ("test", (10,())) :: (String, (Int,())) 
    in getInstance provider 

tryFunc2 :: String 
tryFunc2 = 
    let provider = ("test", (10,())) :: (String, (Int,())) 
    in getInstance provider 
+0

спасибо за повтор. Вы видите какую-либо выгоду от своей реализации по сравнению с реализацией Алека? –

+0

Я мало знаю о прагмах, которые использует Алек, но различие между перекрывающимися и перекрывающимися приводит к тому, что я считаю, что это способ описать порядок решения. Кажется, что есть 2 случая: базовый (перекрывающийся) и специальный (перекрывающий). С семейством типов вы можете иметь большую гибкость, то есть не только 2 варианта, но 3 или более. Например. [в этом случае] (https://gist.github.com/gallais/3b7c7d9488b25978caa278bc18617f79), вы проверяете не только левый, но и правый компонент продукта на равенство. 'tryFunc3' демонстрирует эту новую способность. – gallais

Смежные вопросы