问题
Consider this class, and an example instance.
The purpose is to provide a type level switch that allows to convert the base type — Int
, in this case, — to a proven predicate subtype, for future use. Granted, the class is somewhat contrived, but I extracted it from actual code, and I would populate it with more useful methods, save that I am stuck.
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeApplications #-}
module Collection where
import Data.Tagged
-- $setup
--
-- λ :set -XTypeApplications
class Collected phantom
where
type Element phantom = r | r -> phantom
type Element phantom = Tagged phantom Int
type Collection phantom = r | r -> phantom
type Collection phantom = Tagged phantom [Int]
collection :: Collection phantom
inCollection :: Int -> Maybe (Element phantom)
data Primes
instance Collected Primes
where
type Element Primes = Tagged Primes Int
type Collection Primes = Tagged Primes [Int]
collection = Tagged [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
-- inCollection :: Int -> Maybe (Element Primes)
inCollection element
| element `elem` unTagged (collection @Primes) = Just $ Tagged element
| otherwise = Nothing
-- ^
-- λ inCollection @Primes 7
-- Just (Tagged 7)
-- λ inCollection @Primes 8
-- Nothing
(This is a runnable code complete with repl tests that pass as written.)
I will be dealing with several subtypes over the same base type that only differ by the definition of collection
, while the method of proof is consistently lookup. So there is no reason not to have a default code for that method. However, I was not able to devise such code. My first draft fails to type check, while the second, adjusted draft runs, but does not seem to terminate.
Here is the first draft:
...
{-# LANGUAGE DefaultSignatures #-}
...
class Collected phantom
...
inCollection :: Int -> Maybe (Element phantom)
default inCollection :: ( Element phantom ~ Tagged phantom Int
, Collection phantom ~ Tagged phantom [Int] )
=> Int -> Maybe (Element phantom)
inCollection element
| element `elem` unTagged collection = Just $ Tagged element
| otherwise = Nothing
...
Here is the second:
...
{-# LANGUAGE ScopedTypeVariables #-}
...
class Collected phantom
...
inCollection :: ...
default inCollection :: ...
inCollection ...
where
collection = (collection :: Collection phantom)
(Only the added parts are shown. The corresponding method definition in the instance was removed. In the second draft, the only addition aside from the pragma is the last line that attempts to type collection
.)
What is the problem? What can be done?
回答1:
It appears that the following will work. The idea is to instantiate collection
at the right type in the default (with collection @phantom
) which also requires ScopedTypeVariables
.
Update: I see now that this is what you tried to do with your second attempt. The problem is that your where
clause defined an infinite loop, as the collection
on the RHS is the same collection
being bound on the LHS (insert facepalm as appropriate).
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeApplications #-}
module Collection where
import Data.Tagged
class Collected phantom
where
type Element phantom = r | r -> phantom
type Element phantom = Tagged phantom Int
type Collection phantom = r | r -> phantom
type Collection phantom = Tagged phantom [Int]
collection :: Collection phantom
inCollection :: Int -> Maybe (Element phantom)
default inCollection :: ( Collection phantom ~ Tagged phantom [Int]
, Element phantom ~ Tagged phantom Int)
=> Int -> Maybe (Element phantom)
inCollection element
| element `elem` unTagged (collection @phantom) = Just $ Tagged element
| otherwise = Nothing
data Primes
instance Collected Primes
where
type Element Primes = Tagged Primes Int
type Collection Primes = Tagged Primes [Int]
collection = Tagged [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
来源:https://stackoverflow.com/questions/51502879/how-can-i-move-this-instance-method-definition-to-class-default