我正在尝试将以下地图表示为Haskell函数:
鉴于两种类型a, b
,考虑由函数类型F(a, b)
组成的函数族{/ 1}
f :: a -> a -> ... -> a -> b
n
次重复a
,其中n
是大于零的整数。
我想要的是将f
中的每个函数F(a, b)
映射到函数f' :: [a] -> b
,以便f x1 x2 ... xr = f' [x1, ..., xr]
,其中r
小于参数的数量{ {1}}需要(即我正在寻找函数f
)。如果有多个元素而不是listify :: F(a, b) -> ([a] -> b)
接受参数,则应丢弃其他元素:
f
此外,如果传递列出的空白,则任何值均可接受。
我当然能够为具有固定数量参数的函数实现此映射(例如:f :: a -> a -> b
(listify f xs) == (listify f $ take 2 xs)
等),但我找不到编写执行该函数的函数的方法同时为listify :: (a -> a -> b) -> ([a] -> b)
中的所有f
。尽管Template Haskell可能能够为我提供合适的工具,但我对这样的解决方案并不感兴趣。我想找到一些纯粹的“类型魔法”方法来做到这一点。
有人知道这是否可能?有人可能会指出我正确的方向吗?或者这是一个已知的“问题”,已经解决了数十亿次,而我却无法找到解决方案?
答案 0 :(得分:9)
我们必须在这里挑选毒药。如果我们使用不太安全的pragma,我们可以得到更多的推理,反之亦然;有很多种组合。
首先:使用重叠实例,非函数作为基本情况,但无法处理多态a
类型:
{-# LANGUAGE MultiParamTypeClasses, TypeFamilies, FlexibleInstances #-}
class Listify a b where
listify :: a -> b
instance {-# OVERLAPS #-} r ~ ([a] -> b) => Listify b r where
listify = const
instance (Listify f r, r ~ ([a] -> b)) => Listify (a -> f) r where
listify f (a:as) = listify (f a) as
-- listify (+) [0, 2] -- error
-- listify (+) [0, 2 :: Int] -- OK
-- listify () [] -- OK
第二:使用重叠实例,以函数为基础,可以处理多态类型:
{-# LANGUAGE MultiParamTypeClasses, TypeFamilies, FlexibleInstances, FlexibleContexts #-}
class Listify a b where
listify :: a -> b
instance {-# OVERLAPS #-} r ~ ([a] -> b) => Listify (a -> b) r where
listify f (a:_) = f a
instance (Listify (a -> b) r, r ~ ([a] -> b)) => Listify (a -> a -> b) r where
listify f (a:as) = listify (f a) as
-- listify (+) [0, 2] -- OK
-- listify () [] -- error, first arg must be a function
第三:使用不连贯的实例,在基本情况下具有值,可以处理多态类型:
{-# LANGUAGE MultiParamTypeClasses, TypeFamilies, FlexibleInstances #-}
class Listify a b where
listify :: a -> b
instance {-# INCOHERENT #-} r ~ ([a] -> b) => Listify b r where
listify = const
instance (Listify f r, r ~ ([a] -> b)) => Listify (a -> f) r where
listify f (a:as) = listify (f a) as
-- listify 0 [] -- OK
-- listify (+) [2, 4] -- OK
第四:使用封闭类型系列UndecidableInstances
作为帮助实例解析,具有基本情况的值,无法处理多态类型:
{-# LANGUAGE UndecidableInstances, ScopedTypeVariables, DataKinds,
TypeFamilies, MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}
import Data.Proxy
data Nat = Z | S Nat
type family Arity f where
Arity (a -> b) = S (Arity b)
Arity b = Z
class Listify (n :: Nat) a b where
listify' :: Proxy n -> a -> b
instance r ~ (a -> b) => Listify Z b r where
listify' _ = const
instance (Listify n f r, a ~ (a' -> f), r ~ ([a'] -> b)) => Listify (S n) a r where
listify' _ f (a:as) = listify' (Proxy :: Proxy n) (f a) as
listify :: forall a b. Listify (Arity a) a b => a -> b
listify = listify' (Proxy :: Proxy (Arity a))
-- listify (+) [3, 4] -- error
-- listify (+) [3, 4::Int] -- OK
-- listify () [] -- OK
-- listify 0 [] -- error
-- listify (0 :: Int) [] -- OK
从我的脑海中,除了INCOHERENT
之外,大致可以看到这些是野外可见的变种,因为这在图书馆代码中极为罕见(出于好的理由)。
我个人推荐使用封闭类型系列的版本,因为UndecidableInstances
和类型系列是迄今为止最少争议的语言扩展,它们仍然提供了相当大的可用性。
答案 1 :(得分:5)
实际上它非常简单,甚至不需要重叠实例:
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
class Listifiable f a b where
listify :: f -> [a] -> b
instance Listifiable b a b where
listify = const
instance (Listifiable f) a b => Listifiable (a->f) a b where
listify f (x:xs) = listify (f x) xs
然后你可以做
GHCi> listify ((+) :: Int->Int->Int) [1,2 :: Int] :: Int
3
但是对这些本地显式类型签名的需求可以很好地说明你自己遇到的问题。
(有可能使用FunctionalDependencies
缓解此问题,但至少不能以简单的方式解决。)