Simplify your code: Functional core, imperative shell
41–50 of 219 posts
Re: Simplify your code: Functional core, imperative shell
#42this looks like a post from 2007 im shocked at the date
Re: Simplify your code: Functional core, imperative shell
#43Re: Simplify your code: Functional core, imperative shell
#44Earlier quoted context omitted.
In asynchronous environments, you may not be able to repeat the same query with the same result (unless you control a cache of results, which has its own issues). If some condition is determined by the command’s implementation that subsequent code is interested in (a condition that isn’t preventing the command from succeeding), it’s generally more robust for the command to return that information to the caller, who t…
> it’s generally more robust for the command to return that information to the caller, who then can make use of it. But now the command is also a query. You don't need the command to return anything (though it can be more efficient or convenient). It can set state indicating, "Hey, I was called but by the time I tried to do the thing the world and had changed and I couldn't. Try using a lock next time." if (query(?))…
I’d argue that the separation makes things worse here, because it creates additional hidden state.
Also, as I stated, this is not about error handling.
Re: Simplify your code: Functional core, imperative shell
#45Re: Simplify your code: Functional core, imperative shell
#46this looks like a post from 2007 im shocked at the date
Re: Simplify your code: Functional core, imperative shell
#47this looks like a post from 2007 im shocked at the date
Re: Simplify your code: Functional core, imperative shell
#48Earlier quoted context omitted.
In Elixir this would be written as: db.getUsers() |> getExpiredUsers(Date.now()) |> generateExpiryEmails() |> email.bulkSend() I think Elixir hits the nail on the head when it comes to finding the right balance between functional and imperative style code.
bulk_send( generate_expiry_email(user) for user in db.getUsers() if is_expired(user, date.now()) ) (...Just another flavour of syntax to look at)
Users.all()
|> Enum.filter(&Users.is_expired?(&1, Date.utc_today()))
|> Enum.map(&generate_expiry_email/1)
|> tap(&IO.inspect(label: "Expiry Email"))
|> Enum.reject(&is_nil/1)
|> bulk_send()
The nice thing here is that we can easily log to the console, and also filter out nil expiry emails. In production code, `generate_expiry_email/1` would likely return a Result (a tuple of `{:ok, email}` or `{:error, reason}`), so we could complicate this a bit further and collect the errors to send to a logger, or to update some flag in the db.It just becomes so easy to incrementally add functionality here.
---
Quick syntax reference for anyone reading:
- Pipelines apply the previous result as the first argument of the next function
- The `/1` after a function name indicates the arity, since Elixir supports multiple dispatch
- `&fun/1` expands to `fn arg -> fun(arg) end`
- `&fun(&1, "something")` expands to `fn arg -> fun(arg, "something") end`
Re: Simplify your code: Functional core, imperative shell
#49Re: Simplify your code: Functional core, imperative shell
#50This works right up to the point where you try to make the code to support opening transactions functional. :D Some things are flat out imperative in nature. Open/close/acquire/release all come to mind. Yes, the RAI pattern is nice. But it seems to imply the opposite? Functional shell over an imperative core. Indeed, the general idea of imperative assembly comes to mind as the ultimate "core" for most software. Edit:…
That's not what functional core, imperative shell means though. It's a given that CPUs aren't functional. The advice is for people programming in languages that have expressions - ruby, in the case of the original talk. The functional paradigm mostly assumes automatic memory management.