什么是应用风格的实际用途?
我是一名Scala程序员,现在正在学习Haskell。 很容易find面向对象概念的实际用例和真实世界的例子,例如装饰器,策略模式等。书籍和网站都充斥着它。
我意识到,这不是function概念的情况。 例如: 应用程序 。
我正在努力寻找应用程序的实际使用案例。 到目前为止,我所遇到的几乎所有的教程和书籍都提供了[]
和Maybe
的例子。 我希望应用程序比这更适用,看到他们在FP社区的所有关注。
我想我理解申请者的概念基础(也许我错了),而且我已经等待了我的启蒙时刻。 但似乎并没有发生。 从来没有编程,我有一个时刻,我会高兴地喊,“尤里卡!我可以在这里使用应用程序! (除了[]
和Maybe
)。
有人可以指导我如何在日常编程中使用应用程序吗? 我如何开始发现模式? 谢谢!
警告:我的回答相当讲究/抱歉。 所以起诉我
那么,在你日常的Haskell编程中,你多久创build一个新的数据types? 听起来就像你想知道什么时候创build你自己的Applicative实例,除非你正在编译你自己的parsing器,否则你可能不需要做太多的事情。 另一方面,应用实例,你应该学会经常做。
应用不是像装饰者或策略的“devise模式”。 这是一个抽象,它使得它更普遍,更普遍有用,但是更不明显。 你很难find“实际用途”的原因是因为这个例子使用它太简单了。 你使用装饰器在窗户上放置滚动条。 你使用策略来统一你的国际象棋机器人的攻防动作界面。 但是,什么是应用程序? 那么,他们是更广泛的,所以很难说,他们是什么,没关系。 适用于parsing组合器是非常方便的。 Yesodnetworking框架使用Applicative来帮助build立和提取表单中的信息。 如果你看,你会发现一百万和一个使用Applicative; 全是这个地方 但是因为它太抽象了,所以你只需要感受它,以便认识到许多可以让你的生活更轻松的地方。
应用程序是很好的,当你有一个简单的旧函数的几个variables,你有参数,但他们被包装在某种上下文。 例如,你有简单的旧连接函数(++)
但是你想把它应用到通过I / O获得的2个string。 然后IO
是一个应用函数的事实来拯救:
Prelude Control.Applicative> (++) <$> getLine <*> getLine hi there "hithere"
即使你明确地要求非Maybe
例子,对我来说这似乎是一个很好的用例,所以我举个例子。 你有几个variables的常规函数,但你不知道你是否有所有你需要的值(其中一些可能无法计算,产生Nothing
)。 所以本质上是因为你有“部分值”,你想把你的函数变成一个部分函数,如果它的任何input是未定义的,这个函数是未定义的。 然后
Prelude Control.Applicative> (+) <$> Just 3 <*> Just 5 Just 8
但
Prelude Control.Applicative> (+) <$> Just 3 <*> Nothing Nothing
这正是你想要的。
基本的想法是,你正在将一个正则函数“提升”到一个上下文中,只要你愿意,它可以被应用到尽可能多的参数。 Applicative
对于一个基本Functor
的额外能力是它可以提升任意fmap
函数,而fmap
只能提升一元函数。
由于许多应用程序也是monad,所以我觉得这个问题真的有两面。
当两者都可用时,我为什么要使用应用界面而不是一元界面?
这主要是风格问题。 虽然monad具有do
in的语法糖, do
使用适用的样式往往导致代码更紧凑。
在这个例子中,我们有一个Foo
types,我们想要构造这个types的随机值。 使用monad实例为IO
,我们可能会写
data Foo = Foo Int Double randomFoo = do x <- randomIO y <- randomIO return $ Foo xy
适用的变体是相当短一点。
randomFoo = Foo <$> randomIO <*> randomIO
当然,我们可以使用liftM2
来获得类似的简洁性,但是其应用风格比不得不依赖于matrix特定的提升函数。
在实践中,大多数情况下,我使用应用程序的方式与我使用无点式方式大致相同:避免在将操作更明确地表示为其他操作的组合时命名中间值。
为什么我要使用不是monad的应用程序?
由于应用程序比monad更受限制,这意味着您可以提取更多有用的静态信息。
一个例子是应用parsing器。 鉴于一元语法分析器支持使用(>>=) :: Monad m => ma -> (a -> mb) -> mb
顺序组合,而应用分析器仅使用(<*>) :: Applicative f => f (a -> b) -> fa -> fb
。 types使差异显而易见:在一元语法分析器中,语法可以根据input而改变,而在应用语法分析器中,语法是固定的。
通过以这种方式限制接口,我们可以例如确定parsing器是否接受空string而不运行它 。 我们还可以确定第一个和后面的集合,这些集合可以用于优化,或者像我最近一直在玩的那样,构造支持更好错误恢复的parsing器。
我觉得Functor,Applicative和Monad是devise模式。
想象一下,你想写一个未来的课堂。 也就是说,一个保存要被计算的值的类。
在Java的心态,你可以创build它像
trait Future[T] { def get: T }
在获得价值之前,“获得”阻塞。
你可能会意识到这一点,并重写它来callback:
trait Future[T] { def foreach(f: T => Unit): Unit }
但是如果将来有两种用途会发生什么? 这意味着你需要保留一个callback列表。 另外,如果一个方法接收Future [Int]并需要返回一个基于Int内部的计算,会发生什么? 或者如果你有两个期货,你会做什么,你需要根据他们提供的价值计算一些东西?
但是如果你知道FP概念,你就知道不是直接在T上工作,而是可以操纵Future实例。
trait Future[T] { def map[U](f: T => U): Future[U] }
现在您的应用程序发生了变化,每次您需要处理所包含的值时,您只需返回一个新的Future。
一旦你开始在这条路上,你不能停在那里。 你意识到,为了操纵两个期货,你只需要作为一个应用的模型,为了创造期货,你需要一个未来的单子定义等等。
更新:正如@Eric所build议的,我写了一篇博文: http ://www.tikalk.com/incubator/blog/functional-programming-scala-rest-us
我终于明白了应用程序如何在日常编程中提供帮助:
http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/chunk-html/index.html
作者显示了应用程序如何能够帮助validation和处理失败相结合。
演示文稿在Scala中,但作者还提供了Haskell,Java和C#的完整代码示例。
我认为应用程序缓解了一般代码的一般用法。 有多less次你想要应用一个函数的情况,但函数不是一元的,你想要应用它的值是否是单值的? 对我来说:相当多次!
这里是我昨天刚刚写的一个例子:
ghci> import Data.Time.Clock ghci> import Data.Time.Calendar ghci> getCurrentTime >>= return . toGregorian . utctDay
与使用本申请的这个相比:
ghci> import Control.Applicative ghci> toGregorian . utctDay <$> getCurrentTime
这种forms看起来“更自然”(至less在我眼中:)
来自“Functor”的Applicative将泛化“fmap”,以便轻松expression对多个参数(liftA2)或一系列参数(使用<*>)的作用。
来自“Monad”的Applicative,它不会让计算依赖于计算的值。 具体来说,你不能模式匹配和分支返回值,通常你所能做的就是把它传递给另一个构造函数或函数。
因此,我看到夹在Functor和Monad之间的应用程序。 识别何时不在单值计算的值上分支是查看何时切换到Applicative的一种方法。
下面是一个从aeson包中获取的例子:
data Coord = Coord { x :: Double, y :: Double } instance FromJSON Coord where parseJSON (Object v) = Coord <$> v .: "x" <*> v .: "y"
有一些像ZipList这样的ADT可以具有应用实例,但不是一元实例。 在理解应用程序和monad之间的区别时,这是一个非常有用的例子。 由于如此多的应用程序也是monad,所以没有ZipList这样的具体示例,很容易看不出两者之间的区别。
我认为在Hackage上浏览软件包的来源可能是值得的,并且亲眼目睹在现有的Haskell代码中如何使用应用函子等。
我在一个讨论中描述了一个应用函数的实际使用的例子,我在下面引用。
请注意,代码示例是我的假设语言的伪代码,它将以子types的概念forms隐藏types类,所以如果您看到一个方法调用apply
只需将其转换为您的types模型,例如Scalaz或Haskell中的<*>
。
如果我们使用
null
或none
标记一个数组或hashmap的元素来表示它们的索引或键是有效的而无价值的,那么Applicative
可以在对具有值的元素进行操作的同时,在没有任何样板的情况下跳过无价值的元素。 更重要的是,它可以自动处理任何先验未知的Wrapped
语义,即在Hashmap[Wrapped[T]]
(任何级别的组合,例如Hashmap[Wrapped[Wrapped2[T]]]
因为applicative是可组合但monad不)。我已经可以想象如何使我的代码更容易理解。 我可以把重点放在语义上,而不是所有的东西,让我在那里,我的语义将打开包装的扩展,而你所有的示例代码是不是。
值得注意的是,我之前忘了指出,你之前的例子并没有效仿
Applicative
的返回值,它将是一个List
,而不是Nullable
,Option
或Maybe
。 所以即使我试图修复你的例子也不能模拟Applicative.apply
。记住
functionToApply
是Applicative.apply
的input,所以容器保持控制。
list1.apply( list2.apply( ... listN.apply( List.lift(functionToApply) ) ... ) )
等价。
list1.apply( list2.apply( ... listN.map(functionToApply) ... ) )
而我提出的语法糖就是编译器会转换到上面的。
funcToApply(list1, list2, ... list N)
阅读这个交互式讨论是有用的,因为我不能在这里复制它。 我期望这个url不会中断,因为那个博客的主人是谁。 例如,我引用了进一步的讨论。
大多数程序员可能不希望将语句外的控制stream与分配混为一谈
Applicative.apply用于在types参数的嵌套(组合)的任何级别上将函数的部分应用推广到参数化types(又名generics)。 这是所有关于使更普遍的构图成为可能。 不能通过将function拉到function的完整评价(即返回值)之外来实现一般性,类似于洋葱不能从内到外剥离。
因此,这不是混乱,这是一个新的自由度,目前不可用。 根据我们的讨论线索,这就是为什么你必须抛出exception或将它们存储在全局variables中,因为你的语言没有这个自由度。 这不是这些类别理论仿函数的唯一应用(在我的主持人队列的评论中阐述)。
我提供了一个链接到Scala,F#和C#中的抽象validation示例 ,目前它正处于主持人队列中。 比较令人讨厌的C#版本的代码。 原因是因为C#没有被泛化。 我直观地预期,随着程序的增长,C#特定案例的样板将以几何forms爆炸。
我想我在这里介绍的是一个很好的例子,用于野外发现的Applicative
。