如何在 Scala 中将流畅的界面与函数式风格结合起来?
我一直在阅读Java、JavaScript 和 Scala 我喜欢它的外观,但是一直在努力了解如何将其与 Scala 中更加基于类型/功能的方法相协调。
举一个非常具体的例子来说明我的意思:我编写了一个 API 客户端,可以像这样调用:
val response = MyTargetApi.get("orders", 24)
get()
的返回值是一个 Tuple3
类型称为 RestfulResponse
,如我的 包object:
// 1. Return code
// 2. Response headers
// 2. Response body (Option)
type RestfulResponse = (Int, List[String], Option[String])
这工作得很好 - 我真的不想牺牲元组返回值的功能简单性 - 但我想用各种“流畅”的方法调用来扩展库,也许是这样的
val response = MyTargetApi.get("customers", 55).throwIfError()
// Or perhaps:
MyTargetApi.get("orders", 24).debugPrint(verbose=true)
:我可以将返回类型化元组(或类似元组)的 get()
功能简单性与向 API 添加更多“流畅”功能的能力结合起来吗?
I've been reading about the OO 'fluent interface' approach in Java, JavaScript and Scala and I like the look of it, but have been struggling to see how to reconcile it with a more type-based/functional approach in Scala.
To give a very specific example of what I mean: I've written an API client which can be invoked like this:
val response = MyTargetApi.get("orders", 24)
The return value from get()
is a Tuple3
type called RestfulResponse
, as defined in my package object:
// 1. Return code
// 2. Response headers
// 2. Response body (Option)
type RestfulResponse = (Int, List[String], Option[String])
This works fine - and I don't really want to sacrifice the functional simplicity of a tuple return value - but I would like to extend the library with various 'fluent' method calls, perhaps something like this:
val response = MyTargetApi.get("customers", 55).throwIfError()
// Or perhaps:
MyTargetApi.get("orders", 24).debugPrint(verbose=true)
How can I combine the functional simplicity of get()
returning a typed tuple (or similar) with the ability to add more 'fluent' capabilities to my API?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
看来您正在处理休息风格通信的客户端 API。您的 get 方法似乎是触发实际请求/响应周期的方法。看起来您必须处理这个问题:
我认为对于传输的属性,您可以将其中一些放入
MyTargetApi
对象的构造函数中,但您也可以创建一个 query 对象将存储单个查询的对象,并且可以使用query()
方法以流畅方式设置:这将返回一些存储日志级别值的有状态
Query
对象,错误处理。为了提供输入数据,您还可以使用查询对象来设置这些值,但不返回响应而是返回QueryResult
:然后最终获取您调用
的结果。所以在一天结束时你可以这样调用它
:应该是一个详细的请求,在出现问题时会出错,检索 id 22 的客户,保留正文并以
Option[Int]
形式获取其长度。这个想法是,您可以使用
map
来定义对您尚未获得的结果的计算。如果我们向其中添加flatMap
,那么您还可以组合来自两个不同查询的两个计算。It seems you are dealing with a client side API of a rest style communication. Your
get
method seems to be what triggers the actual request/response cycle. It looks like you'd have to deal with this:I think for the properties of the transport, you can put some of it into the constructor of the
MyTargetApi
object, but you can also create a query object that will store those for a single query and can be set in a fluent way using aquery()
method:This would return some stateful
Query
object that stores the value for log level, error handling. For providing the data for the input, you can also use the query object to set those values but instead of returning your response return aQueryResult
:Then to eventually get the results you call
run
. So at the end of the day you can call it like this:Which should be a verbose request that will error out on issue, retrieve the customers with id 22, keep the body and get its length as an
Option[Int]
.The idea is that you can use
map
to define computations on a result you do not yet have. If we addflatMap
to it, then you could also combine two computations from two different queries.老实说,我认为听起来您需要多摸索一下,因为该示例的功能不明显,也不是特别流畅。看来您可能将流畅性与非幂等混淆了,因为您的
debugPrint
方法可能正在执行I/O,而throwIfError
是抛出异常。你是这个意思吗?如果您指的是有状态构建器是否有效,答案是“不是最纯粹的意义上的”。但请注意,构建器不必是有状态的。
首先;这可以使用命名参数创建:
或者,无状态构建器:
嘿,快点:
至于使用非类型化字符串来定义您正在执行的查询;在静态类型语言中,这是一种糟糕的形式。更重要的是,没有必要:
嘿,急:
虽然,我认为这是一个坏主意 - 你不应该有一个单一的方法可以给你返回概念上完全不同类型的结果
总结:我个人认为没有不管出于什么原因,流畅性和函数式不能混合,因为函数式只是表明缺乏可变状态以及对幂等函数的强烈偏好来执行您的逻辑。
这是给您的一个:
我认为第二个更流畅。如果你定义:
那就是可能的;如果你定义了一个函数。我发现 Scala 中函数和流畅性融合得很好。
To be honest, I think it sounds like you need to feel your way around a little more because the example is not obviously functional, nor particularly fluent. It seems you might be mixing up fluency with not-idempotent in the sense that your
debugPrint
method is presumably performing I/O and thethrowIfError
is throwing exceptions. Is that what you mean?If you are referring to whether a stateful builder is functional, the answer is "not in the purest sense". However, note that a builder does not have to be stateful.
Firstly; this can be created using named parameters:
Or, a stateless builder:
Hey presto:
As to your use of untyped strings to define the query you are making; this is poor form in a statically-typed language. What is more, there is no need:
Hey presto:
Although, I think this is a bad idea - you shouldn't have a single method which can give you back notionally completely different types of results
To conclude: I personally think there is no reason whatsoever that fluency and functional cannot mix, since functional just indicates the lack of mutable state and the strong preference for idempotent functions to perform your logic in.
Here's one for you:
I would argue that the second is more fluent. It's possible if you define:
That is; if you define a function. I find functions and fluency mix very well in Scala.
您可以尝试让 get() 返回一个可能看起来像这样的包装对象
基本上,这允许您将响应放入包含您想要的所有这些好方法的包含中,并且,如果您只是想获得包装的响应,您只需调用包装器的 get() 方法即可。
当然,这样做的缺点是,如果您担心的话,您将需要稍微更改一下 API。好吧...实际上,如果您创建了从 RestfulResponse 到 ResponseWrapper 的隐式转换,反之亦然,您可能可以避免更改 API。这是值得考虑的事情。
You could try having get() return a wrapper object that might look something like this
Basically, this allows you to put your response into a contain that has all of these nice methods that you want, and, if you simply want to get the wrapped response, you can just call the wrapper's get() method.
Of course, the downside of this is that you will need to change your API a bit, if that's worrisome to you at all. Well... you could probably avoid needing to change your API, actually, if you, instead, created an implicit conversion from RestfulResponse to ResponseWrapper and vice versa. That's something worth considering.