[HN Gopher] Effect - Build robust apps in TypeScript
___________________________________________________________________
Effect - Build robust apps in TypeScript
Author : PaulHoule
Score : 102 points
Date : 2024-06-14 16:20 UTC (6 hours ago)
(HTM) web link (effect.website)
(TXT) w3m dump (effect.website)
| tra3 wrote:
| I dont have an opinion on the project itself (yet), but I gotta
| say the website and the introduction is great.
|
| The section on "without effect" and "with effect" is intriguing
| and makes me want to dig in more.
|
| I used to be of the opinion that things should just stand on
| their own but lately I've accepted the fact that the hind brain
| visual appeal is important.
| epolanski wrote:
| This is essentially Scala's ZIO for Typescript.
|
| The huge advantage is Typescript obviously, a much more popular
| language to spread the same ideas.
|
| I've been happily using the effect ecosystem for years and can't
| but say I am super happy to do so.
|
| It does require some patience to learn and it's not really super
| friendly to people that never really took the time to learn
| typescript, but if you did it's such a pleasure to write code for
| Node/Demo/Browser/Workers.
| neya wrote:
| I was going to comment something snarky, but damn, it does look
| good! As someone with Elixir background, it looks really
| attractive to me and I was able to grasp the problem statement in
| just 10 seconds. Impressive presentation format!
| AirMax98 wrote:
| Buried pretty deep in the docs: "Effect's major unique insight is
| that we can use the type system to track errors"
|
| This website should probably start with this. Instead it's a
| bunch of like, modern synergy-ese.
| epolanski wrote:
| From the main page:
|
| https://postimg.cc/sBQB37h6
|
| There's lots to Effect beyond error handling, I can imagine it
| can seem a bit overwhelming.
|
| The great thing is that you can cherry pick what you need.
| prophesi wrote:
| Maximum Type Safety and Error Handling doesn't tell the
| interesting bit that it uses the type system to track errors.
| (And I really don't know what Maximum Type Safety is supposed
| to mean in the first place)
| epolanski wrote:
| I can imagine, how would you improve it though?
|
| Concurrency, runtime, tracked errors, functional
| composition, dependency injection, etc there's lots of
| problems that the ecosystem solves.
|
| It's hard to say which of these features is particularly
| more important.
| adamwk wrote:
| I guess to me it looks like yet another monads for JS
| library. I get its a marketing page but marketing to
| whom? Maybe there's a better document to share on HN that
| shows how Effect improves those features?
| galaxyLogic wrote:
| In plain (ES6) JavaScript I can extend the class Error with my
| own error-classes and then extend those further. So I can
| create a different error-class for ever conceivable error. I
| can also use "instanceof" to infer whether an error is an
| instance of any (recursive) subclass of the error-class I'm
| testing for.
|
| How does EffectTS improve upon that?
| riwsky wrote:
| It makes the errors that a function can fail with part of
| that function's type.
| mind-blight wrote:
| Just reading the docs, but it looks like it forces these
| errors into compile-time checks instead of runtime checks. It
| makes you a lot more confident that code that compiles with
| run correctly and without error.
|
| Seems like it's mostly trying do similar things to what
| Haskell or StandardML does, but in Typescript.
| eyelidlessness wrote:
| The other answers you've gotten are right, and this is mostly
| just expanding on their points. But speaking for a me, this
| is how I view the value proposition:
|
| With Effect (and ideas like it), you can treat error
| conditions as normal expressions, without special control
| flow. You can reason about the error conditions the same way
| you reason about the successful data flow. You can treat your
| own Error subclasses just like any other value type, with the
| same kind of conditional logic in the same flow. And you can
| do that without looking deeper into the call stack to
| understand where errors come from, because errors flow
| exactly where values flow.
|
| Because errors and values are in the same flow, you can
| compose things in ways you couldn't (or at least wouldn't
| typically) with separate error control flow. You can build
| complex logic by composing simpler fundamentals, in a way
| that's more declarative and self-documenting. Your error
| instanceof checks can be combined with map/filter/whatever
| else just like you might use with success values, and they
| can be mixed so that recoverable error conditions are
| expressed even more like success conditions.
|
| If you're into types, all of the same benefits apply at the
| type system level. You don't have to care about types to
| enjoy these benefits. If you don't care now but embrace types
| in the future, you'll just get more of the same benefits in
| that hypothetical future.
| mind-blight wrote:
| That was my first thought too. I thought it was a new web
| framework at first, then an ETL pipeline library. It took me
| about 5 pages of reading before I found an actual description,
| and I only knew what it was trying to accomplish from the code
| example because I've done functional programming before
| PaulHoule wrote:
| You can but should you in some particular way?
|
| It's often said that checked exceptions are the worst mistake
| in Java and I believe that.
|
| To be specific consider the case of a class that implements
| FetchSomeData which fetches some data (could just as well be a
| function with parameters encoded inside it as a closure) which
| could come from many sources such as * hard
| coded in the class * read from a file * fetched
| via http * fetched from Postgresql or CouchDB or ...
|
| the one thing these have in common is they can fail in very
| different ways. If you want to take encapsulation seriously
| here the right way to do it is to expose certain semantics of
| the error such as * Could the user avoid this
| problem by changing their inputs? * What do we tell the
| end user? What do we tell the sysadmin? * Is it likely
| that this problem will clear up if we retry the request in five
| minutes? * Is attempting to use this object likely to
| cause worsening data corruption
|
| and such. In Java we get the very week beer of people creating
| a large number of exceptions like
| FailureinSubsystemSeventeenException and sprinking thousands of
| methods with throws clauses and doing a lot of error-prone
| catching and rethrowing to please the compiler but if you
| really are serious about error handling you know you could get
| a RuntimeException because something wrote a[15] on an array
| that has only 12 elements and you still need to catch those
| anyway.
|
| So far systems that use something like Either[Result|Exception]
| aren't based on an ontology of failure but my experience is
| that code written like that tends to have the same problems you
| have with checked exceptions: harried by the compiler people
| often omit error handling code to the maximum extent that they
| can. From working with both kinds of code bases I'd say that
| code bases worked on exceptions have the glass half full and
| handle errors properly more than they do improperly, these
| monad-based systems are the other way around.
| hn_throwaway_99 wrote:
| I haven't finished the whole intro video yet, but if anyone from
| Effect sees this, curious why y'all went with your own schema
| validation library? I've worked on a bunch of projects across a
| bunch of companies and everyone seems to have standardized on
| Zod. Any thought on making Zod pluggable here?
| epolanski wrote:
| Zod is a great library, you can use it with Effect without any
| issues!
|
| Schema is a more advanced library though that goes beyond
| parsing, it supports both encoding and decoding, also supports
| more complex types.
|
| Check the docs of effect/schema to get a taste.
| srhtftw wrote:
| Indeed both Zod and Schema are great with Effect.
| noname120 wrote:
| Ok this is cool and all but where do I put my breakpoints now
| when something unexpected happens and I need to debug production
| code?
| epolanski wrote:
| You do it the same way you do it in any JS application.
| noname120 wrote:
| Well you can't because now you declaratively represent the
| execution flow, and the actual execution is implicit. If you
| put any breakpoint in there it will break during the
| declaration of the execution flow rather than when the
| corresponding logic actually gets executed:
| Http.request.get(`/todos/${id}`).pipe(
| Http.client.fetchOk, Http.response.json, )
| epolanski wrote:
| You put the breakpoint where you run it with Effect.runX
| then.
| noname120 wrote:
| Yeah, and then it won't step you through your code but
| through unintelligible abstractions and Effect internals.
| Good luck debugging.
| epolanski wrote:
| That's a typical issue when debugging the JS ecosystem,
| it's not really an effect problem per se, albeit since
| Effect comes with a runtime there's way more noise as you
| noticed. It doesn't help your example has only library
| code to test.
| johnfn wrote:
| It is actually Effect-specific, because Effect modifies
| control flow in JS. For instance, if you pipe 3 functions
| in Effect, you'll have to step through the pipe function
| to get from the execution of the first to the execution
| of the next. Working around this adds overhead every time
| you debug your application. In vanilla JS, `a = foo(); b
| = bar(a); c = baz(b)` might not be as pretty, but it is
| trivial to step through.
| galaxyLogic wrote:
| Plus if you are able to debug it somehow, and discover
| the bug in your code, can you fix it right there in your
| debugger?
|
| Or do you need to figure out what is the place in your
| original unprocessed source-code that corresponds to the
| code you see in the debugger, and then switch from the
| debugger to edit the original file and then rebuild and
| rerun everything and see if that fixes the problem?
|
| I wonder if that is a solved problem in plain TypeScript
| either?
| ivanjermakov wrote:
| This is why it was hard for me to work on complex functional
| programming projects: execution flow is not obvious and you
| can't just debug/print in the middle of the function.
|
| Repl is often sufficient, but not in cases where you need to
| pass some convoluted data as input.
| riwsky wrote:
| A fair criticism. To Effect's credit, though, their
| OpenTelemetry integration reports usable spans (i.e. stack
| trace matching the mental model, not the trampolined event
| loop) and associates the logs with them appropriately.
| jauntywundrkind wrote:
| Shout-out to my coworker who highlighted this banger from a
| wonderfully down & dirty review of effect-ts,
|
| > _If you 're on drugs, check out Effect.loop. Someone smart
| asked "How can we make a for loop deterministic and palatable to
| Functional Programmers?" they then smoked crack and did a good
| job._
|
| https://www.linkedin.com/pulse/quick-thoughts-effect-ts-jess...
|
| The review is discursive & dense; I wish we saw more hot take
| rampages through codebases like this.
| EugeneOZ wrote:
| Reminds me of Result in Rust.
| srhtftw wrote:
| I've used Effect-ts for a little under a year.
|
| It's great for workflows with clear points in the logic where
| things should happen and it provides more sophisticated (i.e.
| powerful but harder to master) tools like Fibers and Streams
| which allow you to reason about failure cases of reactive
| asynchronous operations. In many cases it offers a clear path out
| of callback-hell that is more reliable than promises and
| async/await.
|
| However while the Effect-ts docs are getting better and may be ok
| for people with a good knowledge of functional programming and
| Typescript, they are nowhere near the quality they need to be for
| those who don't. People looking for examples online will get
| frustrated because Effect API churn over the past three years has
| made many old posts and articles obsolete. Old github repos won't
| work out of the box. And you better be comfortable with codemods
| if you haven't frozen your Effect-ts version.
|
| Fortunately the Effect-ts Discord channel is full of friendly and
| helpful people and the Effect team provides high quality
| assistance to people who ask for it. It makes me sad this
| treasure trove of information is trapped in Discord where search
| is of little value.
|
| A good book or collection of high quality examples of how to use
| Effect-ts with de-facto standard frameworks like React could help
| its adoption grow significantly.
| mind-blight wrote:
| Do you know if people have tried distributing execution across
| multiple machines using Effect? That's one of the big
| advantages of fully immutable code execution imo, and I'm
| really interested if anyone has succeeded in leveraging Effect
| for that purpose.
| srhtftw wrote:
| I don't believe anyone has done that yet in the way I think
| you're thinking for Effect-ts - i.e. something which would
| give effect execution a kind of location-transparency.
|
| That said, the person who built the ZIO project which
| inspired Effect-ts is now working on an exciting project
| called Golem Cloud1 which aims to provide durable and
| reliable location-transparent execution of Wasm programs.
|
| Mike Stonebraker's DBOS2 looks to provide something similar
| for Typescript.
|
| 1 https://github.com/golemcloud
|
| 2 https://www.dbos.dev
| fellowniusmonk wrote:
| I really just wish groups would train a llama 3 model and have
| AI assisted answers pulled directly from source code. Like if
| this project made https://docs.effect.website and I could query
| it directly secretllama.com style.
| PaulHoule wrote:
| That would require the models to have real reasoning ability
| as opposed to looking up preformed answers on Stack Overflow.
|
| Study after study shows that many evaluations of LLMs are
| deceptive because they evaluate them on questions that were
| answered in the training set. On the other hand, this is
| exactly why they can do so well on medical board exams
| because you don't learn to pass the medical board by
| predicting what medical treatments _should_ work according to
| first principles but instead by remembering which medical
| treatments have been _proven_ to work.
|
| For that matter I've long wanted a conventional search engine
| which can be specialized for a particular software project I
| am working on: for instance I might be using JDK 17 and JooQ
| version 3.15 and I only want to search those versions of
| docs. It shouldn't be hard to look at the POM file to figure
| these versions out. I have the exact same problem with
| Javascript where I have to work with code that uses different
| versions of react-router, bootstrap, MUI, etc. The idea LLM
| coding assistant should do the same.
| benced wrote:
| I just asked GPT 4o to do the following and it succeeded:
|
| > Write me JS code that generates a random list of 10 prime
| numbers, takes the sine of each (as radians), then takes
| the cosine of each of those values (as degrees) and then
| adds 5 to each value
|
| I assure you, this task is not on Stackoverflow (because
| nobody would ever be deranged enough to want to do this)
| but GPT 4o could do it!
|
| Admittedly, this shows only a very rudimentary reasoning
| ability but it demonstrates _some_. The idea that LLMs are
| just a slight evolution of search indexes is demonstrably
| false.
| doomroot wrote:
| This takes 0 reasoning. You've given it an exact map to
| follow to the answer.
| PaulHoule wrote:
| LLMs are also good at tasks that are roughly "linear" in
| the sense that a group of input tokens corresponds to a
| group of output tokens and that translation moves from
| left to right.
|
| In a hard programming problem, for instance, you have a
| number of actions that have to take place in a certain
| dependency order that you could resolve by topological
| sort, but in a problem like the above one bit of English
| corresponds to one bit of Python in basically the same
| order. Similarly if it couldn't translate
| take the sine of...
|
| to Math.sin(x)
|
| because the language didn't already have a sin function
| it would have to code one up. Similarly, translating
| between Chinese and English isn't really that hard
| because it is mostly a linear problem, people will accept
| some errors, and it's a bit of an ill-defined problem
| anyway. (Who's going to be mad if one word out of 50 is
| wrong whereas getting one word wrong in a program could
| mean it doesn't compile and delivers zero value?)
|
| LLMs can do a bit of generalization beyond full text
| search, but people really underestimate how much they
| fake reasoning by using memory and generalization and how
| asking them problems that aren't structurally close to
| problems in the training set reveals their weakness.
| Studies show that LLMs aren't robust at all to the
| changes in the order of parts of problems, for instance.
|
| The biggest problem people have reasoning about ChatGPT
| is that they seem to have a strong psychological need to
| credit it with more intelligence than it has the same way
| we're inclined to see a face inside a cut stem. It can do
| an awful lot, but it cheats pervasively and if you don't
| let it cheat it doesn't seem as smart anymore.
| chaostheory wrote:
| Why would you want to simulate threads instead of using something
| like actors instead?
| darkest_ruby wrote:
| This is nothing but just TaskEither that has existed (among
| others incredibly useful things for years) in fp-ts.
| epolanski wrote:
| It's a good intuition. Indeed you can see it as
| ReaderTaskEither (albeit on steroids).
|
| The fp-ts author is also an effect maintainer and recommends
| effect for new projects. fp-ts is not dead, but it's core goals
| (providing Haskell/PureScript-like types and type classes) have
| been mostly achieved.
|
| Effect is an ecosystem with different goals from fp-ts.
| mixedCase wrote:
| This is for all intents and purposes, fp-ts 3. The projects
| essentially merged, Giulio Canti is working on it and fp-ts 2
| is renaming methods to ease the transition to it.
| sureIy wrote:
| Oof this looks awful and I pity anyone who started projects on
| this. It's so far removed from regular JavaScript you should have
| just written your own language. If/when this project goes under,
| whole codebases need to be binned.
| diob wrote:
| It's one of those things that's great for a solo dev. But I'd
| cringe having to come into a codebase like this. And then have
| to learn all the pitfalls, and onboard folks and have them
| learn the pitfalls. . .
|
| I also am a bit weirded out by the Effect.Effect<unknown,
| HttpClientError>. . . What is unknown?
| eropple wrote:
| `unknown` is a standard type in TypeScript for "it could be
| anything and you don't know what it is, so you need to
| introspect it and assert a type if you want to use it". (As
| opposed to `any`, which is "it could be anything but you _don
| 't_ have to introspect it to use it, you're just opting out
| of the type system".)
|
| This is not "normie" TypeScript but I wouldn't characterize
| it as particularly scary.
| diob wrote:
| I know what unknown is but why isn't it typed? I don't see
| it as an improvement from the well typed thing on the left.
| a-morales wrote:
| It isn't typed since the response from the http call can
| be anything. It could be a Todo, a generic success
| wrapper that contains a Todo, or maybe it was recently
| changed to return a Checklist instead of a Todo. Since
| the response can be anything we have two options, either
| make the type be any or unknown. any is an escape hatch
| typescript provides that allows anything, so if you
| access a field that doesn't exists on the response you'll
| get a runtime exception. With unknown typescript doesn't
| let you do anything with the value until you figure out
| the type. In this example the next step would be to try
| to decode that unknown into a Todo, or fail with some
| error[1]. This guarantees that the object your code
| expects is actually what you are expecting both at
| compile time and runtime.
|
| Given the goal of effect is to provide type safe tracking
| of a codebase; having an any breaks that assumption,
| while unknown forces you to add validation to keep it
| type safe.
|
| 1. https://github.com/Effect-
| TS/effect/blob/main/packages/schem...
| seabrookmx wrote:
| Reminds me of old React codebases that used mobx :')
| gotts wrote:
| I liked the plot comment "Complexity(Lower is better)" because in
| Javascript community it seems like not so obvious to many people
| eiriklv wrote:
| A bit on the side:
|
| Stream processing of data is nicer using a declarative approach.
| Though I've found that describing control flow is often more
| intuitive using an imperative approach. It's a shame that redux-
| saga got tied to react/redux, because the idea of generators and
| declarative effects are universal - useful on both backend and
| frontend.
| RussianCow wrote:
| At various points in my career, I've seriously considered writing
| an effects library very similar to this one because the benefits
| in a sufficiently complex system are clear to me. But ultimately,
| JavaScript doesn't have the functional primitives to make a
| library like this very ergonomic. In particular, the lack of
| syntactic sugar for function composition (e.g. a "pipe" operator
| such as |> as seen in functional languages) means every operation
| needs to be composed via method calls and argument passing, which
| adds a ton of visual noise that detracts heavily from the
| conceptual simplicity of the library.
|
| If your project, as a whole, benefits heavily from a functional
| style of programming, you are likely better off using one of the
| many functional programming languages that compile to JavaScript,
| like ReScript, OCaml, F#, or Scala.
| johnfn wrote:
| Hm, maybe I'm not seeing the point here, or maybe I'm just too
| old and crotchety. But it seems to me that Effect provides two
| things:
|
| 1. A way to have strongly-typed errors
|
| 2. A standard library, like a better HTTP library and better
| concurrency primitives.
|
| I'm not particularly sold on 1) in isolation. If you want
| strongly typed errors, you can use vanilla TS: just use `type
| Result<T> = { type: 'result'; data: T } | { type: 'error';
| message: string }` as a return type and you're basically there,
| modulo an if statement on the calling side.
|
| On the other hand, 2) seems a bit more compelling. I do think
| it's true that JS could use a better standard library. But the
| cost of the standard library is that I now have to write my code
| in this extremely unusual way, and that seems like a bit too high
| of a cost to me. Effect.forEach (instead of for...of),
| Effect.runPromise (instead of await), Effect.andThen (instead
| of... nothing?) have, to my eyes, significant penalties in
| readability, debuggability and maintainability.
| steve_adams_86 wrote:
| Something which gets me about these Effect methods is that very
| similar things can be accomplished with generators. I assume
| this is all generators under the hood. I personally would
| rather just write that code because once you're familiar with
| it, it's really not that difficult to work with.
|
| I agree that a wrapper around generators is nice if it suits
| the situation (like async/await), and abstracting them is
| useful at times, but I'm not sure I'd want to pull in an entire
| library for it.
|
| One thing, the Effect.runPromise and similar methods which seem
| like overkill are probably providing quite a bit more utility
| and reliability than it appears. It might offer good cleanup
| guarantees as well.
|
| I should add that this is the equivalent of a hot take. If I
| looked closer I'm sure I'd change my mind, but I'm old and
| crotchety like you.
| apozem wrote:
| The operator chains remind me of RxJS. Which is great! I love
| reactive programming. But speaking as someone who spent two years
| teaching RxJS to a team of frontend newbies, the learning curve
| is _brutal_.
|
| (Please do not reply with "I found RxJS easy". Good for you.)
| nwienert wrote:
| I once used RxJS for a dev tool platform, we had a use case
| where we had to take a tree-like structure of user data and
| recursively resolve all the async nodes.
|
| Took it to the RxJS discord after a couple days of pounding my
| head on it. One of the creators was there and was super
| helpful.
|
| We went back and forth on the problem at least 6 times each,
| with new attempts. He tried quite a few variations, but none
| ever worked.
| LoganDark wrote:
| > (Please do not reply with "I found RxJS easy". Good for you.)
|
| What about "what makes it hard"? I've never tried RxJS before,
| so I'm curious to know where the learning curve is.
| fire_lake wrote:
| Without do-notation this is kind of dead on arrival no?
| nielsbot wrote:
| What I really want JS/TS is one of my favorite features about
| Swift:
|
| Throwing functions in Swift don't throw traditional exceptions
| (please someone up the stack catch this). Throwing an error in
| Swift actually returns an Error to the calling function instead
| of that function's normal result type. Swift also enforces that
| when you call code that might "throw" (return an error instead of
| a result) you either handle the error or "rethrow" it.
|
| In TS I use this pattern:
|
| `type ErrorThrowingFunction = <T,>() => T|Error`
|
| But it's not nearly as nice to use in TS since it's not a
| language feature.
| ozuly wrote:
| For me personally, I find a functional programming to be the most
| productive when working on a project as a solo dev or as part of
| a very small team that I've hired. But the downside is that it's
| much harder to onboard devs without experience in functional
| programming. It takes a while before they can be productive when
| introduced to a codebase that's mostly written in a functional
| style -- one that's written in Effect for example.
|
| I recently started a company and was tempted to use Effect as I
| felt I would be able to build faster and higher quality than
| Standard TypeScript, but decided against it since I felt that I
| would pay the price when it came to grow the team. So far the
| decision has paid off.
|
| I did indulge myself a little bit though and do make heavy use of
| remeda and ts-pattern across my code.
___________________________________________________________________
(page generated 2024-06-14 23:02 UTC)