Please log in to watch this conference skillscast.
Functional programming relies on building programs from orthogonal, composable blocks. That's likely one of the reasons why full-blown application frameworks haven't gained much traction in the functional ecosystem.
However, we still need to structure our code and wire up our applications in a way that lets us keep them modular, testable and simply pleasant to work with - in this talk, we will learn how to do just that!
In this talk, we will walk through the architecture design and testing setup for a functional app on the Typelevel stack that integrates with several third-party services to process data in a streaming fashion, and expose its results to downstream clients.
Q&A
Question: Does Scala power Disney+?
Answer: I wouldn't be here if it didn't ;)
Question: What is the font used for the code snippets in the slides?
Answer: It's the jetbrains font in vscode, although I don't use ligatures (for some reason Keynote didn't allow me to turn them off)
Question: Personally, I’m not a fan of tagless final style. I prefer the “old way”: passing dependencies to class constructor
Answer: To each their own, to me using Reader to pass dependencies was clunky and boilerplate-y. With meow-mtl, there's even more machinery than with implicits. And there's definitely lots of machinery with ZIO layers, you just don't see it.
I prefer implicits (F or no F) because it's a native language feature, and as such it's specified quite well in the language specification, unlike the details of how ZLayers compose :)
But yeah, you can apply a lot of the same practices with ZIO layers too
Question: Is using https://cir.is/ for config, after PureConfig and ZIO Config why do we need another functional config library?
Answer: zio-config is obviously more friendly to existing zio users, and pureconfig uses configuration files in a different language (HOCON). I'm more of a fan of configuration as code, it has fewer gotchas (classpath ordering doesn't really matter, for one) and more type safety. I'm not sure if zio-config does that as well
Question: I am new to Scala. Is “capability traits” a term you created? Is there any similar terminology being used by the community? Also, the practice of using context bound to pass a dependency that only have one possible instance. Does that pattern has a name?
Answer: I've seen that term used around fs2 and other libraries, so definitely not me
For the former, I think it's just it - using context bounds. Or the tagless final pattern (which is kind of different as it's used in Scala than what the original author of the "tagless final" term meant)
You could call the capability traits "lawless type classes", since that's essentially what they are - some operations defined for a type, without any algebraic laws governing them.
Question: Can you explain why using capability traits is preferred over lawful type classes? Apologies if you already explained this in your talk. You mentioned it in this slide (https://speakerdeck.com/kubukoz/connecting-the-dots-building-and-structuring-a-functional-application-in-scala?slide=36), but I missed the rationale behind it
Answer: Sync and Async are super powerful type classes. The more power a TC has, the fewer types can implement it. If it has fewer operations (or ones that are easier to define for a variety of types), it's going to be more generic that way.
For example, you can implement Async
for IO
, monad
transformers, a free monad... but that's roughly as far as it goes - you can't
get Async
for Option
.
Also, using the more powerful thing is against the principle of least power, so
instead of giving your method (like def findUsers[F[_]: Network]
)
a couple operations, you could give it the whole world of Async
-
which is capable of lifting any operation into F
, so
basically all the power there is in the world.
This goes away the moment you make your F
concrete, btw - you have
Async
built in.
Sync/Async are FFI type classes, as they allow you to do any external world
interaction you want, and lift that into an effect. Other type classes like
Monad
and Functor
are insanely less capable (you can
only introduce values via pure
and operations on the result, or
composing effects that you got from other interfaces), and thus very fine to
use.
Finally, if you ever decide to make something not be a capability
trait, it's going to be easier to provide a fake instance than if you had
Async
there in the first place - you can't mock Async
(well, can but shouldn't, and I really wish you don't), but you could implement
a "virtual" ProcessRunner
or Network
for tests.
Final point, e.g. in fs2 all the File operations require Files[F]
,
and if they required Sync
/`Concurrent` instead it'd mean you'd
have Sync
/ Concurrent
everywhere too - unless you
wrapped it in your own Files
.
Anyway, the point is constraining the operations to a subset of what your effect
is capable of - to limit your knowledge about what kind of a type it could be,
and making assumptions specific to that data type. Later on it can pay off in
fake-ability... and it usually helps separate concerns too. Instead of
having Async
you can split your usage into Files
and
Network
, for example, and that helps when you have more convoluted
code.
Resources:
If someone wants to get familiar with the app I'm going to use as the example, feel free to dig in (it's not very well documented yet though, I'll try to make that better over the upcoming days): https://github.com/kubukoz/dropbox-demo
YouTube Channel: https://www.youtube.com/channel/UCBSRCuGz9laxVv0rAnn2O9Q
YOU MAY ALSO LIKE:
Connecting the dots - building and structuring a functional application in Scala
Jakub Kozłowski
Scala DeveloperDisney Streaming Services