8 ms·
Maybe Functions
- zubairq 3y agoAfter reading this I realise that I am guilty of building many "maybe" functions in my own code. Definitely something I need to be aware of
- dolmen 3y agoGo has a related idiom: the "Must" functions https://pkg.go.dev/text/template#Must https://pkg.go.dev/text/template#Must
- botpag 3y agoWhat on earth did I just read
- yellow_lead 3y agoMaybe an essay
- syhol 3y agoJust an essay, Nothing else
- koliber 3y agoAnother option is Exceptions. The function either does what it's supposed to, or freaks out. You can remove the null checks and the software will raise a null pointer exception. In the first example, could raise a NotLoggedInException. It's still a maybe function, but you have a mechanism for expressing the why-notness of the function run, as opposed to returning a generic null. As an aside, I prefer the "Unless" model of thinking vs the "Maybe" model of thinking. It's biased towards success. It presumes that the function is most likely to do something unless a precheck fails. filterBestFriendsUnless vs maybeFilterBestFriends. getUserUnless vs maybeGetUser. If we go this far down the rabbit hole, we can assume there's always an "unless". Programs run out of memory, stacks have limited depth. There are maybe conditions for which we can not account.
- pavel_lishin 3y agoI think that's true for checked exceptions; in Typescript, I'd rather see that a function may return a null, rather than get surprised by a possible exception that's not telegraphed.
- Sakos 3y agoI think that's my biggest problem with exceptions. I have to rely on the doc comments to figure out whether a method can throw exceptions and which and when. And who knows if that covers all the possible exceptions from all the code that method relies on. It entirely sidesteps the type system and means I can't rely on the input/output types when using a method.
- dataflow 3y ago> I have to rely on the doc comments to figure out whether a method can throw exceptions But you still have to rely on the docs to tell if a function can abort execution (say, by calling std::optional<T>::value() when there's no value). And an unhandled exception would abort just the same. Where do you see there being a difference? > and which and when. Maybe types don't tell you that either, you still need documentation for that. Even worse, Maybe types cannot tell you that unless they're leaf-ish functions. Because they may call opaque functions (such as your own callbacks) for which they have no such knowledge to begin with. Thus they have to support propagating some type-erased error type... which is exactly what exceptions do. So, again: how is the situation different?
- AtNightWeCode 3y agoI am for exceptions but it should not be used for basic control flow. Many techs will treat all exceptions as errors.
- eru 3y agoInterestingly, Python uses exceptions for basic control flow, like end of for-loop.
- yellow_lead 3y agoAn alternative I've used or seen used in Java is to put @Nullable on the function. The caller knows the result could be null, and must check for it. Linters/Static analysis can verify when you haven't checked it as well. There's an urge to return Optional<Object> but now you must check Optional.isPresent AND object != null.
- jmduke 3y agoAgreed with this essay, and I think it rhymes with two others that I've found pretty influential over the past five years: 1. Parse, don't validate (https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...) 2. Pipeline-oriented programming (https://fsharpforfunandprofit.com/pipeline/ https://fsharpforfunandprofit.com/pipeline/) In my experience, the "best" code (defining "best" as some abstract melange of "easy to reason about", "easy to modify", "easy to compose", and "easy to test") ends up following the characteristics outlined by the sum of these three essays — strictly and rigorously elevating exceptions/failures/nulls to first-class types and then pushing them as high in the stack as possible so callers _must_ deal with them.
- blowski 3y agoWhat constitues the "best" code depends on the incidental complexity of the problems you're trying to solve. Great code is when you have just enough of all those things, but have too much or too little and the code is worse.
- jmduke 3y agoYou're right, of course — there are parts of my codebase that flagrantly disregard these rules, and did so for good reasons that I don't regret. But I've found that while "everything is relative and should be situated in the context of the problem you're trying to solve" is a useful truism, it makes for poor praxis. It's hard to improve existing code or develop newer engineers without _some_ set of compasses and heuristics for what "good code" is, and once you develop that set the patterns and strategies for implementing "good code" naturally follows.
- blowski 3y agoI agree, I'd imagine as a senior you have a good sense of what counts as good enough. Unfortunately, there are too many would-be seniors justifying horrendous amounts of accidental complexity as "good practice".
- 3y ago
- croes 3y agoNot everthing that can return null is a maybe function, sometimes you need a difference between zero and nothing. Solution3 for their example: function maybeRenderBestFriends() { const user = maybeGetUser(); if(user!=null){ const friends = maybeGetFriends(user); const bestFriends = maybeFilterBestFriends(friends); return render(bestFriends); } return null; }
- pavel_lishin 3y agoWhat's the advantage of the Monad approach? Doesn't the render function still have to check whether those Maybes contain values or not?
- valenterry 3y agoIf you just have 1 step then there is no advantage. If you have multiple steps then the advantage is that you never have to unpack "in the middle" and you don't have to care - and the compiler has your back. Classical example: show the street number of the user or show <None> if there is no street number. There can be multiple things missing on the way and multiple transformations might happen on the way. E.g. the user might not even have an adress saved alltogether. In that case, you only have to "check whether those Maybes contain values or not" once at the very end.
- steeleduncan 3y agoI dont think there is any advantage when the language lacks syntax level support for Monads. E.g. in Haskell (which does) it would look roughly like: getUser :: Maybe User getUser = ... getFriends :: Maybe [User] getFriends = do user <- getUser let friends = getFriendsForUser user return friends bestFriends :: Maybe [User] bestFriends = do friends <- getFriends let besties = filter isBestFriend friends return besties render :: IO () render = do let bffs = getBestFriends case bffs of Just besties -> renderBestFriends besties Nothing -> renderNoFriends The Maybe monad itself contains the equivalent of runSafely from the article, and the syntax propagates the failure case transparently from getUser down to the choice of render function used. All without either the hassle of handling null cases, or the danger that you might forget to handle them and the code crash. Without syntax level support, its not obviously an improvement to me
- chrischen 3y agoIt saves you from having to write the null check code for other cases.
- ValentinTrinque 3y agoYou do have another solution that can lower the amount of conditions: Null Objects. These don’t fit every use cases, but they can allow you to express what’s missing, or not defined, or empty, and avoid nil pointers dereference or conditions to check the state. As Sandy Metz is used to say « Nothing is Something »[0] 0: https://youtu.be/OMPfEXIlTVE?si=qmizH1OvqV7eLKNK https://youtu.be/OMPfEXIlTVE?si=qmizH1OvqV7eLKNK
- ramchip 3y agoFrom the article: > This is highly related to the "Null Object Pattern", but I thought I would explain it from the perspective of functions.
- TYMorningCoffee 3y ago> Here’s a specific example, it’s a “maybe” function as it only returns the friends of a user, if the user is logged in. Basically it introduces a possible return null. function maybeGetUser(): User | null { if (!loggedIn) { return null; } return fetchUser(); } I believe this is an error. The code sample I took from the article is about getting a user, not the user's friends. Since that function will return a list, an empty List might work.
- lixquid 3y agoProgram logic fundamentally has to contend with different conditions. Sometimes the user will be logged in and have friends, sometimes they won't. The "maybe" style has the inconsistency embedded in the type system; it's impossible to have an invocation to getFriends and then not handle the resulting possibility of not being logged in. Shifting it up to the caller just means that you're going to have to remember to ensure the user is logged in before calling getFriends otherwise you'll get some kind of error, which might give you more control, but now there's no guarantee in the type system that you've handled the case where the user isn't logged in. Writing ifs everywhere to handle failure conditions might be a bit of a pain, but that's more of a failing of the language than the style.
- nnnnico 3y agoI agree with this. I prefer explicit optional/null return types, otherwise the possibility of an error still exists but is now hidden to the caller. Langs with syntax for concise optional chaining and such fix this problem imo
- madeofpalk 3y agoNote that Typescript - the language of the examples - does have proper null through it's type system. A function that returns `T | null` will require callers check for null before using T.
- bluetomcat 3y agoThe proliferation of conditional "maybe" functions is a sign that your call graph is contrived and unnatural. You shouldn't be checking "userLoggedIn == true" in each and every accessor function. Ideally, such checks should bubble up towards the top of the call stack, and be performed once in an event loop iteration. The calling code should make sure that some basic prerequisites are met.
- luc4 3y agoTo be pedantic: Their Maybe class is just a Functor, not a Monad.
- tubthumper8 3y agoWhat's it missing? flatMap/bind/whatever that's called?
- luc4 3y agoYes, exactly. It's missing the monadic bind (which for the list monad would be flatMap).
- moomin 3y agoI feel like there's a whole genre of essays (red vs green functions is the worst example) that could be summarised as: * Monads naturally arise out of many problems in programming. * But I don't want my language to support monads. * So here's something you can do to stay in denial about how much you need monads. At least this example only involves writing hard-to-analyse code and doesn't lead to you trying to invent green threads.
- mrkeen 3y agoThe kicker here is that the author implemented a functor and called it a monad. So of course readers are going to think "the monad approach" is confusing and stay away.
- voxl 3y agoI mean even if you implement a more standard Monad interface plenty of functional programmers still find working with Monads to be ugly. It's really not a solved area.
- moomin 3y agoI’ll accept that. But do notation is the closest thing to sensible we have, whereas most of these articles are just constantly trying to chip away at part of the problem in the hope that they’ll be able to make the whole mountain disappear one stone at a time. And to date, I don’t find the evidence promising that they can. I’m not wedded to stuff like monadic state, I think that might be a bridge too far for regular programming (and besides which, it doesn’t really generalise anyway) but that still leaves a large family of issues that we’re all aware of but trying to dodge.
- somishere 3y agoAnd what if `fetchUser` hits an error? At the very least pop your de-maybeifier after the async call. Or use something language standardised (like a promise in JS where you can just throw). I'm all for a perfy shortcut / early return but this maybe just seems like an abstraction on a non-issue.
- somishere 3y ago... And TBH the bigger smell is eminating from the explicit `null` returns. Next essay please.
- tuyiown 3y agoThis is a pattern you cannot always avoid, due to react, but i don't think it should be normalized. Two remarks: • the render function is omitted, this pattern as a huge impact on application behaviors, if not for display-as-you-load issues, on DOM hidden state (things like focus, animations, etc…) for web apps. • App's do have a global state, with self-consistency, scattering it in a mixed match of loading cache and self contained components just make it hard to work with. I think it's better to have a centralized upper level parent component that manage the transitional initialization states and consistency, not necessarily for the whole app, but at least for the whole displayed UI content.
- aziis98 3y agoI can't wait for more languages to adopt the "?" operator [1] like the Rust one. It's just syntactic sugar for "if expr null return null" but makes it far easier to write code in a more monadic style. (mostly waiting for this in JS and Go) [1]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator https://doc.rust-lang.org/reference/expressions/operator-exp...
- fernandotakai 3y agoi remember using this back in my groovy days https://groovy-lang.org/operators.html#_safe_navigation_operator https://groovy-lang.org/operators.html#_safe_navigation_oper...
- madeofpalk 3y agoGo actually needs proper nil/null/optionality first before it can get syntax features to deal with them.
- mrkeen 3y agoI can't wait for more languages to simply not include null at all. It makes trying to spot them in static analysis and runtime checks much easier, because you no longer need either.
- kibwen 3y agoIn the context of the parent's example, Rust doesn't include null at all, it just has a standard Option type with a None variant. While the other mentioned languages (JS and Go) do have null, they don't necessarily need to remove it to start getting the benefits, they just need to provide standardized alternatives and get the community to follow along (and if, say, a fancy new ? operator only worked on these new types and didn't work on null in general, that would be a strong carrot).
- ubertaco 3y agoThe example of Rust is one where no types are nullable by default, but only if you wrap them in an `Option<T>`. Given these two function signatures: pub fn getUser() -> User pub fn getMaybeUser() -> Option<User> This code won't compile, because `getUser()` cannot return a `None`: pub fn foo() -> Option<String> { let user = getUser()?; return user.name } But this code will compile: pub fn foo2() -> Option<String> { let user = getMaybeUser()?; return Some(user.name) } (rust playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=8e1e919c56b1f0945abede5113f94850 https://play.rust-lang.org/?version=stable&mode=debug&editio...)
- fbn79 3y agoI think the post is related tothe "Null vs Maybe" problem. See https://web.archive.org/web/20230329075114/https://www.nickknowlson.com/blog/2013/04/16/why-maybe-is-better-than-null/ https://web.archive.org/web/20230329075114/https://www.nickk... already discussed here https://news.ycombinator.com/item?id=5577364 https://news.ycombinator.com/item?id=5577364
- cowsandmilk 3y agoExcept it isn’t. Returning ‘User | null’ in typescript is the same type-wise as returning Maybe<User>
- cowsandmilk 3y agoCan’t disagree more. Solution 1 just presents risk that some calls getUser() without doing the log in check. Then what happens? It is false that getUser being a “maybe function” forces the other functions like getFriends to be maybe functions. Don’t let them take null in their arguments. Force the caller to deal with the null when it is returned by getUser.
- jbandela1 3y agoI would argue that the vast majority of functions in real world software are maybe functions in that they can fail. You need to be able to deal with failure. Not only can the user not be logged in, there can be a network issue, etc that makes even downstream functions fail. Also, you have to deal with developer mistakes and what happens when they call incorrectly. This can be something as simple as getting the first element of a collection. What happens when the collection is empty? You can adopt the C++ approach of “undefined behavior” but it turns out to be dangerous. Monads provide a nice disciplined way to dealing with this and composing together functions that can potentially fail. Thankfully, newer languages such as providing support for monads and older languages are evolving features/libraries for monadic error handling.
- planede 3y ago> Also, you have to deal with developer mistakes and what happens when they call incorrectly. There is only one safe(ish) way to deal with programmer errors: crash. Hopefully loudly and early enough so it gets discovered in testing.
- lixquid 3y agoWhy wait until then? The loudest and earliest crash you can get is a failure to compile with a rich type system (obviously, when possible).
- maxcoder4 3y agoI assume you don't write device drivers or operating systems? Predicting every possible failure reason for a function is impossible. Every function is a maybe function.
- kibwen 3y agoIf enumerating every possible failure mode of a function is impossible, then that would underscore the importance of failing fast and dynamically restarting components in order to provide robustness in the face of unforeseeable errors.
- n4r9 3y agoIn C# the closest analogy I can think of is the "Try" pattern. For example, you have int.Parse(string) which returns int, and int.TryParse(string, out int) which returns bool. The fact that the returned value is the validation is a strong incentive to do something with it.
- aronhegedus 3y agothat's a good example!
- tobr 3y agoYou can address this with explicit parameterization instead of global state. That way the missing data is an obvious type error rather than a surprise in the middle of a running function.
- PartiallyTyped 3y agoThis is a great example of the issue with using monads and monad-like patterns in languages that don't have proper support via language constructs for these. In rust for example, this is trivially handled with the questionmark postfix operator — which is just sugar for match — whereas in languages like JS and Java, stacking Optionals and so on can be rather painful as all this sugar is done manually.
- planede 3y agoWhat seems that the fundamental problem is that the functions depend on global state that is not explicitly passed in (is the user logged in?). Maybe an explicit session parameter could work better here. You only have a session when the user is logged in, so you can't even pass anything to the functions otherwise. It can of course be passed further recursively.
- skerit 3y ago> “Functions should do something, not maybe do something…” But it did do something, it checked if the user logged was logged in first.
- augustk 3y agoWirth entered the chat: Procedures should do something. Functions should return something. https://en.wikipedia.org/wiki/Command%E2%80%93query_separation https://en.wikipedia.org/wiki/Command%E2%80%93query_separati... By the way, I have never understood the practice of using a verb in the name of a (pure) function; naming the function after its result using a noun or adjective phrase makes much more sense.
- redleader55 3y agoI find it very interesting that the higher they are in the stack, the more people tend to talk about algebra (monads, functors, etc). I wonder why is that the case? Doesn't kernel or firmware require this level of abstraction?
- mrkeen 3y agoThe author is conflating two separate concepts, and calling them both "the maybe function": 1) functions which do hidden things outside of their contract and/or whose implementation doesn't properly line up with their types. 2) functions which (by necessity) cannot always return the desired value and must return something else instead. > The maybe function is a subtle monster that spreads it’s tentacles across the code-base. This applies to both points 1 and 2. > It’s alternating functionality of “does/doesn’t do something” makes code hard to understand, maintain and debug. This only applies to point 1. > They seem to be trivial to add, but difficult to remove. But hopefully this illustrates the concern and ways to fix it. This cannot apply to point 2, because you cannot take a function which might return a user and `fix` it to make it always return a user. > Solution 2 - Monads This is not a monad. He has implemented the Maybe Functor. runSafely most likely corresponds to map in whatever library you're using, not flatMap. This is visible from its type signature: > runSafely(fn: (val: T) => V): Maybe<V> // map which should be > runSafely(fn: (val: T) => Maybe<V>): Maybe<V> // flatMap And it's also visible in the example function: function getUser(): User { if (!loggedIn) { return null } return fetchUser(); } ... which still suffers from points 1 and 2. Because it's the same function which was highlighted as bad code at the top.
- brlebtag 3y agomaybe the author does not know wtf he is talking about...
- bakhy 3y agoThis looks too easy, the first solution. If there is no logged on user, which User object is fetchUser going to return? Which friends? At the top level, if I were to forget to check if someone is logged in, who knows what would happen here. I've worked on codebases where people were so allergic to the "billion dollar mistake" of nulls, that they created empty objects to return instead of returning null. This bit us in the ass a couple of times, e.g., when caller code was mistakenly passing the wrong ID variable into a fetch method, and just happily continued working and writing garbage into the DB, because it did not realize that its fetch had actually failed. It took data from the empty result object and happily continued its computation with it.
- duncan-donuts 3y agoI’ve had limited success with the null object pattern but there is one case that it worked really well for me. I worked on a feature that was highly dynamic and users could compose reports selecting data points from tangentially related models. Null objects were a really helpful pattern because it was hard to anticipate how models would be composed and if a developer made a mistake it was hard to notice there was no effect. Our null objects would raise exceptions in development and explain what you need to change but wouldn’t prevent execution in production. You could easily argue we should have just presented this exception to the user in all cases but this is where we landed. It’s probably the only case this pattern was beneficial for me.
- another-dave 3y ago> This looks too easy, the first solution. If there is no logged on user, which User object is fetchUser going to return? Which friends? At the top level, if I were to forget to check if someone is logged in, who knows what would happen here. It feels like the most likely thing to happen is that the `getUser()` call would throw a Null Pointer Exception? I think the author is avoiding the pitfall of the NullObject pattern applied incorrectly with solution #1 because they're not masking the 'null-ness' in the code further down, they're just assuming that `null` will never get passed as a value. If it is, code blows up & then gets patched.
- nyanmatt 3y agoI use maybe functions a lot for things like "maybeShowReminderDialog". The conditions for displaying the reminder are wrapped in this maybe function. Surely that's simpler than specifying those conditions before every call to show this dialog, resulting in plenty of duplicated code. And if those conditions change, there is only one place I need to update it. Of course I can make a single operation to check those conditions like "shouldShowReminder", but that too is doubling the surface area of this code. I see the merit of the argument here but disagree with the absolutist stance against "maybe" functions.
- withinboredom 3y agoThere's that. And it also reduces the chance for race conditions: if ( shouldShow() ) /* state changes here where it should not show */ doShow();
- metalrain 3y agoYou can also handle optionality when using the value. function maybeRenderBestFriends() { const user = maybeGetUser(); if (!user) { return null } const friends = maybeGetFriends(user); const bestFriends = friends ? filterBestFriends(friends) : null; return bestFriends ? render(bestFriends) : null; }
- Waterluvian 3y agoOne thing I like about typescript is that unless you’re a masochist, it basically pushes you towards option 1. If you have to constantly check for null/undefined it gets annoying and you naturally think about narrowing the state space so entire sections of your program don’t have to think about those possible states. It should also become obvious when you have a possible null/undefined state and it’s super unclear what that piece of your program ought to do about it other than alert the parent (such as throwing an error). If a component doesn’t have a role to play when null, maybe it shouldn’t ever be seeing null as a possible state.
- otter-in-a-suit 3y agoRespectfully, I don't think this articles uses monads correctly, because it's not using any. This could be very elegant: getUser: Option[User] getFriends(u: User): Seq[Friend] bestFriends(f: Seq[Friend]): Seq[Friend] renderFriends(f: Seq[Friend]): Option[UI] // Unit or type UI or HTML or ... Only `getUser` actually returns an option and is explicit about it. `renderFriends` could arguably do without. To call, we can do bestFriends: Option[Seq[Friends]] = getUser.flatMap(u: User => renderFriends(bestFriends(getFriends(u)))) The render function could either gracefully render an empty list or error check as part of the `flatMap`, which takes the form of flatMap[B](f: A => Option[B]): Option[B] I really, really dislike it when functions signatures are lying to me, since `User` is clearly != `Option[User]` and `null` will not fit the type semantics of `User`, whatever those are. And if you don't _call_ it mondads (but rather something more approachable), it's not that wild and scary sounding a concept all of a sudden. That way, your compiler error checks null-type scenarios for you, your type signatures are clean, don't lie, and your compiler forces you to explicitly do something like `runSafely` (or `runUnsafe` etc.), usually a single point of failure. Bonus, `MonadError`-type constructs are awesome too, since I get handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A] type functions (this is from cats in scala) to deal with errors explicitly.
- shiandow 3y agoIf anything the real lesson here is that you shouldn't try to lift your functions manually in the presence of a Monad. Monads tend to be somewhat 'infectious' in that anything that touches the Monad will need to be monadic. It's the reason why 'nullable' and 'async' can end up transforming most of the code-base to support their use. And if you are going to write a sum type do it properly. If the language doesn't provide sum types but does have function types just use the category theory definition: function maybeWithUser<T>(withUser: User => T, default: () => T): T { if (!loggedIn) return default() return withUser(fetchUser()); } Wrap this in a class if you really want to, but the idea is the same. This then results in pretty much the code he lists in example 1, exactly because most of the functions are just regular functions: function Page() { const bestFriends = maybeWithUser(user => { const friends = getFriends(user); const bestFriends = filterBestFriends(friends); return render(bestFriends); }, null); return <> {bestFriends} </>; } Of course sometimes it's better to just use what you have rather than try to use language features that aren't quite there. It helps if you can recognise what's going on though.
- giankam 3y agoI got stuck at option 1. Rendering code becomes lot more complex. Also few errors that make it difficult to follow the essay, like "it’s a “maybe” function as it only returns the friends of a user" but the function is getUser, not getFriends. Or function getFriends(user: User): Friend[] { return fetchUser(); } The body of the function is wrong.
- tubthumper8 3y agoI know it's a simple example, but the Maybe class should probably use a null check internally rather than a truthy check, so that types like number and string which have falsy values that are nonetheless valid for a use case can be used with Maybe.
- noelwelsh 3y agoI'm surprised the option (pun intended) that immediately came to my mind was not discussed: change the getUser function so it has a "LoggedInUser" parameter, instead of pulling the User from some global state. Then (so long as you have a type system) you can't call the function without the user being logged in.
- jupp0r 3y agoGreat point, but I want to bring up another one I'm seeing all the time: Maybe functions that don't have maybe in their name and just silently don't do something without informing the caller. This is extremely common and the source of many bugs. If your function is a maybe function, name it accordingly.
- jolt42 3y agoFor collections, returning [] (empty collection) instead of null is almost always what I want.
- zem 3y agothis is basically like bubbles under wallpaper - the "maybeness" is not due to your code, but due to the underlying problem you are solving (a user can either be logged in or not, and if they are not then all user properties are null). the article identifies the problem with various solutions as extra layers of abstraction, but i feel like the real issue is ceremony - whether propagating maybe-coloured functions through your code and checking the return value everywhere, or wrapping everything in a monad, you have to do something to handle the null case when all you really care about is the non-null case, and that something inevitably feels like clutter and overhead.
- wesselbindt 3y agoThat's not a monad, that's just a functor. And that's great, because functors are easier to grok than monads! A functor F consists of two things: - a kind of function on types which transforms any type T to some new type F(T) - a rule which associates to any function f: T -> S a function fmap(f): F(T) -> F(S) That's exactly what the author defines here, to a type T we associate Maybe<T>, and to a function f: T -> S we associate `fmap(f)(x) = None if x is None else f(x)`. A monad needs some structure in addition to fmap, namely bind and return. These allow you to take a function T -> F(S) and a function S -> F(U) and compose them together to a function T -> F(U).
- deleted 3y ago[deleted]
- aleksiy123 3y agoI feel like this isn't really a general statement on maybe functions but unnecessary maybe functions. If you can make it not "maybe" but "always" then of course that's obviously better. The real use case is when it really is maybe. (Network call, error handling). Then it's about forcing people to deal with that in a typesafe way and not hiding that it really is maybe.
- gardenhedge 3y agoI think that naming functions with a prefix of maybe is awful. I treat all functions as "maybes".