为什么没有函数的类型类?


17

在一个我一直在纠缠的学习问题中,我意识到我需要一个具有用于应用,编写等操作的函数的类型类。原因...

  1. 将函数的表示形式视为函数本身就是方便的,因此隐式应用函数将使用解释器,而组成函数会得出新的描述。

  2. 一旦有了函数的类型类,就可以为特殊类型的函数派生类型类-就我而言,我想要可逆函数。

例如,应用整数偏移量的函数可以由包含整数的ADT表示。应用这些功能仅意味着将整数相加。通过添加包装的整数来实现合成。逆函数的整数取反。身份函数包装零。无法提供常量函数,因为没有合适的表示形式。

当然,它不需要像值是真正的Haskell函数那样拼写事物,但是一旦我有了主意,我认为这样的库必须已经存在,甚至可以使用标准拼写。但是我在Haskell库中找不到这样的类型类。

我找到了Data.Function模块,但是没有类型类-只有一些常见的函数也可以从Prelude中获得。

那么-为什么没有函数的类型类?是“仅仅是因为没有”还是“因为它没有您想象的那么有用”?还是这个想法存在根本性的问题?

到目前为止,我想到的最大可能的问题是,实际功能上的功能应用程序可能必须由编译器进行特殊处理,以避免出现循环问题-为了应用此功能,我需要应用功能应用程序功能,为此,我需要调用函数应用程序函数,然后执行此操作...

更多线索

示例代码显示我的目标...

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}

--  In my first version, Doable only had the one argument f. This version
--  seemed to be needed to support the UndoableOffset type.
--
--  It seems to work, but it also seems strange. In particular,
--  the composition function - a and b are in the class, but c isn't,
--  yet there's nothing special about c compared with a and b.
class Doable f a b where
  fwdApply :: f a b -> a -> b
  compDoable :: f b c -> f a b -> f a c

--  In the first version, I only needed a constraint for
--  Doable f a b, but either version makes sense.
class (Doable f a b, Doable f b a) => Undoable f a b where
  bwd      :: f a b -> f b a

  bwdApply :: f a b -> b -> a
  bwdApply f b = fwdApply (bwd f) b

--  Original ADT - just making sure I could wrap a pair of functions
--  and there were no really daft mistakes.
data UndoableFn a b = UFN { getFwd :: a -> b, getBwd :: b -> a }

instance Doable UndoableFn a b where
  fwdApply = getFwd
  compDoable f g = UFN ((getFwd f) . (getFwd g)) ((getBwd g) . (getBwd f))

instance Undoable UndoableFn a b where
  bwd f    = UFN (getBwd f) (getFwd f)
  bwdApply = getBwd

--  Making this one work led to all the extensions. This representation
--  can only represent certain functions. I seem to need the typeclass
--  arguments, but also to need to restrict which cases can happen, hence
--  the GADT. A GADT with only one constructor still seems odd. Perhaps
--  surprisingly, this type isn't just a toy (except that the whole thing's
--  a toy really) - it's one real case I need for the exercise. Still a
--  simple special case though.
data UndoableOffset a b where
  UOFF :: Int -> UndoableOffset Int Int

instance Doable UndoableOffset Int Int where
  fwdApply (UOFF x) y = y+x
  compDoable (UOFF x) (UOFF y) = UOFF (x+y)

instance Undoable UndoableOffset Int Int where
  bwdApply (UOFF x) y = y-x
  bwd (UOFF x) = UOFF (-x)

--  Some value-constructing functions
--  (-x) isn't shorthand for subtraction - whoops.
undoableAdd :: Int -> UndoableFn Int Int
undoableAdd x = UFN (+x) (\y -> y-x)

undoableMul :: Int -> UndoableFn Int Int
undoableMul x = UFN (*x) (`div` x)

--  With UndoableFn, it's possible to define an invertible function
--  that isn't invertible - to break the laws. To prevent that, need
--  the UFN constructor to be private (and all public ops to preserve
--  the laws). undoableMul is already not always invertible.
validate :: Undoable f a b => Eq a => f a b -> a -> Bool
validate f x = (bwdApply f (fwdApply f x)) == x

--  Validating a multiply-by-zero invertible function shows the flaw
--  in the validate-function plan. Must try harder.
main = do putStrLn . show $ validate (undoableAdd 3) 5
          putStrLn . show $ validate (undoableMul 3) 5
          --putStrLn . show $ validate (undoableMul 0) 5
          fb1 <- return $ UOFF 5
          fb2 <- return $ UOFF 7
          fb3 <- return $ compDoable fb1 fb2
          putStrLn $ "fwdApply fb1  3 = " ++ (show $ fwdApply fb1  3)
          putStrLn $ "bwdApply fb1  8 = " ++ (show $ bwdApply fb1  8)
          putStrLn $ "fwdApply fb3  2 = " ++ (show $ fwdApply fb3  2)
          putStrLn $ "bwdApply fb3 14 = " ++ (show $ bwdApply fb3 14)

该应用程序涉及一种统一形式,其中统一值不相等,但是通过可逆函数(Prolog样式的逻辑,但具有a = f(b)约束而不是)相互关联a = b。大部分成分将来自优化联合查找结构。逆的需求应该很明显。

如果统一集中的任何项目都不具有确切值,则只能相对于该统一集中的另一个项目对特定项目进行量化。这就是为什么我不想使用“真实”函数-计算那些相对值的原因。我可以放弃整个函数方面,只包含绝对数量和相对数量-我可能只需要数字/向量,并且(+)-但我的内部架构宇航员想要他的乐趣。

我再次断开链接的唯一方法是通过回溯,并且一切都是纯的-联合查找将使用键中的IntMap“指针”完成。我已经完成了简单的联合查找工作,但是由于我还没有添加可逆函数,因此这里没有列出它。

我无法使用Applicative,Monad,Arrow等的原因

我需要函数抽象类提供的主要操作是应用程序和组合。听起来很熟悉-例如Applicative (<*>)Monad (>>=)Arrow (>>>)都是合成功能。但是,在我的案例中,实现函数抽象的类型将包含一些表示函数的数据结构,但该数据结构不是(也不能包含)函数,并且只能表示某些有限的函数集。

正如我在代码说明中提到的那样,有时我只能量化一个项目相对于另一个项目,因为“统一”群集中没有任何项目具有确切的值。我希望能够导出该函数的表示形式,通常是由几个提供的函数(在并集/查找树中向上移动到一个共同祖先)和几个逆函数(向后向下移动到另一个)的组成项目)。

简单的情况-原始的“函数”仅限于整数偏移量“函数”,我希望将组合结果作为整数偏移量“函数”-添加组件偏移量。这就是为什么组合功能和应用程序功能都必须在类中的重要原因。

这意味着我不能提供的业务purereturn或者arr对我的类型,所以我不能使用ApplicativeMonad或者Arrow

这不是那些类型的失败-这是抽象的不匹配。我想要的抽象是一个简单的纯函数。例如,它没有副作用,也不需要为对所有函数进行等效的标准(。)等效构建用于对函数进行排序和组成的便捷符号。

可以举例Category。我有信心,尽管我可能不需要,但我的所有功能都能提供一个身份。但是由于Category不支持应用程序,因此无论如何我仍然需要一个派生类来添加该操作。


2
叫我疯了,但是当我想到像您在描述的类型类是用于应用和组合等时,我想到了应用函子,即函数。也许这就是您要考虑的类型类?
吉米霍法2013年

1
我认为这Applicative是不对的-它要求将值和函数都包装在一起,而我只想包装函数,而包装的函数实际上就是函数,而包装的函数通常不会(在在最一般的情况下,它们是描述函数的AST)。where <*>具有type f (a -> b) -> f a -> f b,我想要一个类型为g a b -> a -> bwhere 的应用程序运算符,ab指定包装函数的域和共域,但是包装器内部不是(不一定)真正的函数。在Arrows上-可能,我来看一下。
Steve314 2013年

3
如果您想求逆,那不是意味着一个群体吗?
jk。

1
@jk。很重要的一点是,意识到关于函数逆函数有很多东西要阅读,这可能会导致OP找到他想要的东西。是有关该主题的一些有趣的阅读材料。但是Google for Haskell函数反函数提供了很多好奇的阅读内容。也许他只是想要Data.Group
Jimmy Hoffa,

2
@ Steve314我认为带有组合的函数是一个单调类别。如果域和共域始终相同,则它们是一个monoid。
Tim Seguine 2013年

Answers:


14

好吧,我不知道有任何新的想法可以将自己推销为代表“功能y”的事物。但是有几个接近

分类目录

如果您有一个具有标识和组成的简单功能概念,那么您将拥有一个类别。

class Category c where
  id :: c a a
  (.) :: c b c -> c a b -> c a c

缺点是,你不能用一组对象的创建一个漂亮的类实例(ab,和c)。我可以创建一个自定义类别类。

如果您的功能具有乘积概念并且可以注入任意功能,那么箭头适合您

 class Arrow a where
   arr :: (b -> c) -> a b c
   first :: a b c -> a (b, d) (c, d)
   second :: a b c -> a (d, b) (d, c)

ArrowApply 有一个应用程序概念,它对您想要的东西很重要。

应用程式

应用程序具有您的应用程序概念,我已在AST中使用它们来表示功能应用程序。

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f b -> f c

还有很多其他想法。但是一个常见的主题是建立一些表示您的函数的数据结构,然后将其传递给解释函数。

这也有多少个单子工作。如果您觉得自己很勇敢,我建议您对它们进行戳记,它们是您所建议的东西的有力工具,实际上可以让您使用do表示法构建数据结构,然后将其折叠成具有不同功能的副作用。但是美丽之处在于,这些功能只是在数据结构上运行,而不是真正意识到如何实现这些功能。这就是我建议您使用的口译员示例。


类别似乎缺少应用程序- ($)。箭看起来乍看之下像是巨大的杀伤力,但ArrowApply听起来还是很有希望的-只要我不需要提供任何我不能提供的信息,那就可以了。暂时+1,需要做更多检查。
Steve314

3
@ Steve314类别确实没有应用程序,但是monads缺少通用的运行方式,并不意味着它们没有用
Daniel Gratzer 2013年

我不能使用Applicativeor Arrow(或Monad)的常见原因是-我不能包装一个普通函数(通常),因为我类型的值表示一个函数但由数据表示,并且如果不支持任何函数有一种翻译的方法。这意味着我不能提供purearrreturn为实例。顺便说一句-这些类很有用,但我不能将它们用于特定目的。Arrow并不是“过大的杀伤力”,这是我上次尝试阅读本文时(当时还没准备好理解)的一种错误印象。
Steve314

@ Steve314提供monad接口来建立数据的想法是免费monad的用途,请检查一下
丹尼尔Gratzer

我观看了Haskell Exchange 2013的视频-安德烈斯·洛(AndresLöh)肯定很好地解释了这一点,尽管我可能仍需要再次观看,玩弄技巧等。不过,我不确定这里是否需要。我的目标是使用不是函数(但具有解释器函数)的表示形式来抽象函数。我不需要副作用抽象,也不需要清晰的符号来进行测序操作。一旦使用了该函数抽象,就可以在另一个库中的算法中一次完成应用程序和组合。
2013年

2

如您所指出的那样,此处使用Applicative的主要问题是没有合理的定义pure。因此,Apply被发明了。至少,这是我对此的理解。

不幸的是,我没有对的情况下的手的例子Apply是不是也Applicative。据称这是正确的IntMap,但我不知道为什么。同样,我不知道您的示例(偏移整数)是否接受Apply实例。


这读起来更像是一条评论,请参阅“ 如何回答
gna15年

抱歉。这或多或少是我有史以来的第一个答案。
user185657

您如何建议我改善答案?
user185657

考虑进行编辑以帮助读者了解您的答案如何解决所问的问题:“为什么没有函数的类型类?是“仅仅是因为没有类型类”?还是“因为它没有您想象的那么有用”?或者这个想法有根本的问题吗?”
t

1
我希望这会更好
user185657

1

除了提到的CategoryArrowApplicative

我也Data.Lambda被Conal Elliott 发现:

一些类似于函数的类,具有类似于lambda的构造

当然看起来很有趣,但是没有示例很难理解...

例子

可以在Wiki页上找到有关有形价值(TV)的示例,这些价值似乎是导致创建TypeCompose图书馆的原因之一。请参阅输入和函数值输出

电视库的想法是以切实的方式显示Haskell值(包括函数)。

为了遵循关于不发布裸机的StackOverflow规则,我在下面复制了一些位,这些位应该给出以下内容:

第一个示例如下:

apples, bananas :: CInput Int
apples  = iTitle "apples"  defaultIn
bananas = iTitle "bananas" defaultIn

shoppingO :: COutput (Int -> Int -> Int)
shoppingO = oTitle "shopping list" $
            oLambda apples (oLambda bananas total)

shopping :: CTV (Int -> Int -> Int)
shopping = tv shoppingO (+)

给出运行时的方式runIO shopping(有关更多注释,GUI和更多示例,请参见此处):

shopping list: apples: 8
bananas: 5
total: 13

请问该如何解决这个问题?看到怎样回答
蚊蚋

@gnat我认为其中的定义Data.Lambda为类函数(需要)提供了类...我不确定如何使用这些东西。我对此进行了探索。但是,它们可能没有为函数应用程序提供抽象。
imz-伊万·扎哈拉里舍夫(Ivan Zakharyaschev),2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.