Polyvariadic Functions in Haskell

淺唱寂寞╮ 提交于 2019-12-02 19:28:15

The idea behind functional dependencies is that in a declaration like

class Collapse a r | r -> a where
  ...

the r -> a bit says that a is uniquely determined by r. So, you can't have instance Collapse (a -> r) (a -> r) and instance Collapse a (a -> r). Note that instance Collapse (a -> r) (a -> r) follows from instance Collapse a a for the complete picture.

In other words, your code is trying to establish instance Collapse t t (the type variable's name is of course unimportant) and instance Collapse a (a -> r). If you substitute (a -> r) for t in the first instance declaration, you get instance Collapse (a -> r) (a -> r). Now this is the only instance of Collapse with the second type parameter equal to (a -> r) that you can have, because the class declaration says that the first type parameter is to be deducible from the second. Yet next you try to establish instance a (a -> r), which would add another instance of Collapse with the second type parameter being (a -> r). Thus, GHC complains.

If you're still experimenting with this, here's an example of constructing a polyvariadic function from a function taking a list, without requiring either a wrapper type or undecidable instances:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}

class Variadic a b r | r -> a where
    variadic :: ([a] -> b) -> r

instance Variadic a b (a -> b) where
    variadic f x = f [x]

instance (Variadic a b (a -> r)) => Variadic a b (a -> a -> r) where
    variadic f x y = variadic (f . (x:)) y

vList :: (Variadic a [a] r) => r
vList = variadic id

vFoldl :: (Variadic b a r) => (a -> b -> a) -> a -> r
vFoldl f z = variadic (foldl f z)

vConcat :: (Variadic [a] [a] r) => r
vConcat = vFoldl (++) []

main = do
    putStrLn $ vConcat "abc" "def" "ghi" "jkl"
    putStrLn $ vList 'x' 'y' 'z'
    if vFoldl (&&) True True True True then putStrLn "Yes" else putStrLn "No"
    if vFoldl (&&) True True False True then putStrLn "Yes" else putStrLn "No"

The downsides to this approach are that the type checker must be able to infer the type of the result (or you have to annotate it), and that it fails badly on polymorphic numeric constants; the reasons for both problems are discussed in the article you mentioned. Don't know if you'll find that helpful, but I was tinkering with polyvariadic functions earlier and remembered this question.

Michał Marczyk is absolutely correct about the fundeps and instance matching issue, and the wrapper type seems like an easy fix. On the other hand, if you're already reading Oleg's site, you might prefer to go deeper down the rabbit hole and try writing an instance for "any type that isn't a function".

As far as UndecidableInstances goes, the coverage condition is described here; it should be obvious why your instances fail it. Note that the word "undecidable" here means undecidable in roughly the same sense as in "the Halting Problem is undecidable"--that is to say, you're telling GHC to recklessly attempt to resolve code that could send it into an infinite loop based only on your assertion that it's okay. It's fun for hacking neat ideas, but consenting to be a human first-pass type-checker for GHC is a burden I personally find wearying.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!