These are chat archives for ramda/ramda

24th
Jul 2017
James Forbes
@JAForbes
Jul 24 2017 00:33

@aretecode I like to work under the assumption that any moment a fundamental abstraction we rely on may change. I learnt from my OO mistakes not to taxonomize, but instead to let the code be code. Arbitrary division can make it a lot harder to recover from these fundamental abstraction changes because related things are scattered. There's also a noun oriented lossiness, where assigning a name to a chain of relationships is less descriptive than just observing the chain of relationships in their original habitat. So while naming things and splitting them up feels nice, its actually not good for you I think.

So at a high level, here's my criticism of your refactor. Its feels easier to read, but its harder to maintain. The names aren't accurate, the comments will go out of date, the divisions are in the wrong places and the design isn't guarding against the future.

This is the risk of code golfing. You simply cannot take code out of a project, out of context and work on it in isolation.

This sort of traversal is happening many times in this route in different sections. Its so common place that giving things names and comments and spacing it out makes it harder to see patterns in the architecture and correct opportunities for refactoring. I say correct opportunities for refactoring because dividing things into their component parts too early can obstruct observations that are far easier to see when they are in their raw form.

The particular structure Editable Loadable Validatable Modifiable Saveable, and their specific layering is constantly in flux. So if I change that order I get some helpful type errors that point me to the exact line that is the problem, and everything to do that with particular problem is right next to it, I don't have to reconstruct the composition in my head but scanning all over the file replacing names with expressions, it's just there inline.

Also, now that I've changed that composition to match the new structure (in numerous places because we split the composition up), those comments may no longer be accurate. Its very easy to fix the code and forget to fix the comments. So now some poor team member diligently reads the comments and (the now potentially out of date variable names) and is under the wrong impression about how the system works. The comments have led them down one path when the initial lump of code would have been much quicker to decipher in the long run.

Let's get into specifics:

const validate = Either.map(
  pipe(Validatable.fromValidatable, Modifiable.fromModifiable)
)

This name isn't accurate. Its actually the exact opposite of Validation. Its saying "I don't care if this is valid or invalid, modified or unmodified for this section of code"

Note how in that description how often "this" came up in that sentence. Now me coming back to this code 3 months from now will believe that code validates things. But it doesn't at all. I might even think I can make a util of it and reuse it in other places. But its usefulness is highly specific to a local context (hence this) so its better to leave it be.

const load = Loadable.fold(
  Either.Left('Loading ' + label + ' Data'),
  Either.Right
)

Again, this is not accurate at all. This code doesn't load anything. I think at this point we need to decide, does naming things and making them small have any benefit other than feeling good. This code is a delayed conditional, and its really important to have that in context, particularly in javascript where we don't have a type system, it can be hard to even realise where an Either came from because its hidden behind a name somewhere like validate in the caller code.

I think this criticism holds for the rest of the code.

@jonahx I am not too happy with able names either but I needed to differentiate the case name from the type name ( especially in Javascript ). And having a repeating suffix makes it clear which layer of abstraction we are dealing with. Again, I think it would feel nice to remove the able.

Also, I include the full name Validatable.fromValidatable because there are other fromValidatable functions on different namespaces that behave very differently, again, context matters.
James Forbes
@JAForbes
Jul 24 2017 00:39
I think this discussion is worth having, I'm glad you are challenging me on this @aretecode :D. I switched sides on this, I used to have this debate with other programmers and I would have been firmly on your side. But I tried it this way, and I've seen how much easier it is to respond to changes in requirements. I work with very novice programmers and they constantly iterate on code like this without issue. I study what trips them and what doesn't and its surprising. It informs everything I do. So much of what we do to accomodate our team, is doing the exact opposite.

I think using an ORM for SQL db's is a related problem. It seems like it's solving a problem by hiding the underlying complexity of the SQL. But embracing SQL itself solves so many problems and makes responding to requirements changing far quicker.

And that's not to say you can't break things up into small pieces, I believe you can. But its best not to do that until you see a pattern repeat itself several times in different contexts that you've found a potentially stable atomic reusable abstraction.

Jonah
@jonahx
Jul 24 2017 01:14
@JAForbes :thumbsup: to chesterton’s fence — i’ve never heard that one before. i am right in the middle of you and @aretecode on this debate. I do agree that “duplication is better than the wrong abstraction.” but once you have an abstraction, being able to pull it out and name it is not just a matter of feeling good. it can radically simplify cognitive load and make working with code much more pleasant. i think context is really important here. notice i haven’t definitively said that either the code is suboptimal or even that the names should be changed — only that i wasn’t a fan of “able” names in general. I feel I’d need intimacy with the project to know if I preferred code like your snippets or @aretecode’s in those specific places. And while I would prefer to get rid of the “able”s I did consider there might be a “least of all evils” kind of reason for it in your case.
in any case, i love the discussion and think you’re making interesting points
James
@aretecode
Jul 24 2017 01:16

@JAForbes the names aren't accurate because it's hard for me to read in the first place and I didn't write it :-P

code golfing, not sure that's the word if it's not trying to make it super small...?

over-abstraction-everything-as-generic reduces your actual problem solving on a given domain

describing the problems with terms that represent and convey the intention is at the juicy core of ddd

sure, validate and load, easy to rename :-) nice link to the related :+1: if there are different places in different namespaces where you have fromValidatable, instinctively it seems like an area for ambiguity and confusion
I can totally appreciate the minimalist approach without the names, and how it has potential to be easier to maintain if you fully are on-board for writing all code in the same max-generic style, it's just a very different style, and I usually think there is a happy medium for most things somewhere in the middle, not on the complete end of an extreme
Jonah
@jonahx
Jul 24 2017 01:23
one other thing that should be made explicit is that the correct choice here depends highly on the developers, their skill level, and their shared culture (or lack thereof). learning J, and seeing what the experts in that community are comfortable with, has opened up my mind to the vast differences that are possible.
James
@aretecode
Jul 24 2017 01:24

but different things for different people, different teams made up of a wide range of skillsets and preferences. If it's consistent, easy to write, easy to maintain, easy to understand, solves the problems, it does the important things :-) many ways to do the same thing.

it's not naming it as an abstraction, it's naming it to make it convey its intention, and make it easier (as @jonahx said, cognitive overhead), I find when it all looks like math, it's not as fun, but some people love it looking like so

It's hard to tell anyone that everyone should do something one way to rule them all (even though some people, ramda especially, have some excellent rulebooks on an opinionated consistent standard)

@jonahx exactly :+1:
personally I find it hard to read code that says a, b, c -> (a, => c) ~ d =-~> z) adding fatigue reading a formula, until I'm overly-familiar with it in a way that gives me the Curse of knowledge
I have an article on the naming and curse of knowledge and stuff in progress, close to done
Johnny Hauser
@m59peacemaker
Jul 24 2017 02:27
I have something that calls navigator.geolocation.getCurrentPosition and another thing that does getCurrentLocation().then(reverseGeo). I have implemented this to cache both calls for the same duration (a few minutes). That way if you call the second one, since it calls the first internally, you have setup the cache for both, or if you called the first, then at least that part is cached so only reverseGeo needs to be called.
Anyone got a nice functional approach to such a thing?
James Forbes
@JAForbes
Jul 24 2017 02:27

sure, validate and load, easy to rename

The 2 hardest problems are cache invalidation, naming things and off by one errors. :D

Sometimes naming is helpful, sometimes it feels helpful but isn't. I think were you to come up with a name that accurately describe what was going on, it'd probably look a lot like the source code. And theres' a huge advantage of reading the source to reading a name. Because the name relies on a model that in the future, you or your team may not have. The "better" the name, the greater the probability it comes with an associated mental model. And if I update the code, I don't have to also sit there and think of the new perfect name, I'm done.

And I'm not saying never name things. Because I do name things. But know its a trade off. And in the context of this project I'm confident with the trade off I'm making.

if there are different places in different namespaces where you have fromValidatable, instinctively it seems like an area for ambiguity and confusion

That is absolutely true, but instinctively, that fence shouldn't be there right? Again context matters.

@jonahx

being able to pull it out and name it is not just a matter of feeling good. it can radically simplify cognitive load and make working with code much more pleasant.

Yep definitely. I really was saying in and of itself naming isn't an inherent good, and this particular refactor wasn't achieving much other than feeling good in my opinion. That's why I was saying "And that's not to say you can't break things up into small pieces, I believe you can. But its best not to do that until you see a pattern repeat itself several times in different contexts that you've found a potentially stable atomic reusable abstraction."

But nothing was repeated here in this sample. So I think splitting it up in this instance is ill advised, but does feel good.

I think there's other problems with breaking an expression into statements, aside from naming things. But that's another tangent. :D

it can radically simplify cognitive load

This is the feels good part. But it isn't actually reducing cognitive load if the abstraction has been sliced at the wrong boundary. Its creating more cognitive load it just seems simpler. And we'll never know if it was the right boundary to slice, and it rarely is. We get this right 1% of the time I think. And we don't discover the problem with our refactor until way down the line - so we have this false confidence that we are all really good at refactoring, its an illusion of time.

There are some metrics that can help choosing those boundaries. But if they aren't there, it really is ill advised. Sometimes code is just complex and we need to accept it, read it, understand it before modifying it. Its so easy to modify things without understanding them when they've been abstracted incorrectly.

@m59peacemaker maybe make the cache bracketed by time, so Math.round( getTime()/<some factor> ). Then you just memoize it. When the amount of time you want to pass has passed the call will fail memoziation and do the fetch
Because your geo data really is a function of time, so we're just making that explicit
Johnny Hauser
@m59peacemaker
Jul 24 2017 02:30
The problem is the dependency
I need A() to cache for 7 minutes, then B(A()) to be cached for the same 7 minutes, and the A cache is also the same in both cases
The simple thing would be just to get all of it in both cases, always B(A())
and just have that cached/memoized for 7 minutes
I want that exact thing, except I only want to call B() if I need it.
Denis Stoyanov
@xgrommx
Jul 24 2017 02:47
try rx,most
Johnny Hauser
@m59peacemaker
Jul 24 2017 02:48
I suspected that was going to have something to do with it :)
I can't afford the bytes in this case
But now that you say that, I think I could figure it out if I could use streams. My interest was primarily educational. My ugly code works fine.
Denis Stoyanov
@xgrommx
Jul 24 2017 02:50
I don’t understand your case) I need A() to cache for 7 minutes, then B(A()) to be cached for the same 7 minutes, and the A cache is also the same in both cases
Johnny Hauser
@m59peacemaker
Jul 24 2017 02:50
User says "show me stuff near me" which is getCurrentPosition, henceforth "A"
A minute later, says the same thing
That is a useless call. There is no way I have anything of interest to him based on the distance he moved in a minute
Similarly, he might say "Show me stuff in Nashville, TN"
Oops, I take that back, kinda
Let's say he is in Nashville, and says "Show me stuff in my city"
Denis Stoyanov
@xgrommx
Jul 24 2017 02:51
fromNavigator().distinctUntilChanged()… I don’t think that exists wrapper for navigator in Rx
Johnny Hauser
@m59peacemaker
Jul 24 2017 02:52
So, I again need to look up where he is, but then also reverseGeo that position from lat, lon into "Nashville, TN"
But if he asks again to show stuff in his city within a few minutes, I would just do "Nashville, TN" and assume he didn't change cities that fast
In both cases getCurrentPosition is called, and should kick off the cache if it doesn't exist already
and then reverseGeo might be called or was the thing that got called, too, and it should be in the same cache
Denis Stoyanov
@xgrommx
Jul 24 2017 02:54
okay
Johnny Hauser
@m59peacemaker
Jul 24 2017 02:55
So, like I said, it would be easier to have just one thing that needs to be cached, and it's got all of it { lat, lon, city, state, etc }
but I really don't want to call reverseGeo unless necessary
Denis Stoyanov
@xgrommx
Jul 24 2017 02:55
Observable.fromEvent(button, ‘click’).withLatestFrom(Observable.fromEvent(element, ‘input’) (_, v) => v).distinctUntilChanged().switchMap(x => fromNavigator(x))… fromNavigator should be Observable or Promise
Adam Szaraniec
@mimol91
Jul 24 2017 05:02
Thanks Kevin, it was what I need!
James Forbes
@JAForbes
Jul 24 2017 05:48
I think the memoization strategy I said originally still makes sense @m59peacemaker
const minutes = 1000*60
const memoize = function(f){
  const index = {}

  return function(...args){
    const k = 
      args.map( a => a+'' ).join('|')

    if( !(k in index) ){
      index[k] = f(...args)  
    }

    return index[k]

  }
}

const now = 12864638831537142

const getAMemoized = memoize(function getA(time){
  console.log('requestingA')
  return Date.now()
})

const getBMemoized = memoize(function getB(time, a){
  console.log('requestingB')
  return Date.now()
})

const by7min = x => Math.floor(x / (7 * minutes) )

getAMemoized(by7min(now)) //requestingA
getAMemoized(by7min(now + 3 * minutes))
getAMemoized(by7min(now + 5 * minutes))
getBMemoized(by7min(now + 5 * minutes), getAMemoized(by7min(now + 5 * minutes)))
// requestingB
getBMemoized(by7min(now + 5 * minutes), getAMemoized(by7min(now + 5 * minutes)))

getBMemoized(by7min(now + 7 * minutes), getAMemoized(by7min(now + 7 * minutes)))
//requestingA
//requestingB

getBMemoized(by7min(now + 7 * minutes), getAMemoized(by7min(now + 7 * minutes)))
Jonah
@jonahx
Jul 24 2017 06:34
@JAForbes one more philosophical point about our discussion from before — something i’ve pondered before. i wonder if the well-named, good version of a DDD approach is essentially piggy-backing off standard language and our familiarity with it as much as possible. because of this, it will be easy to read to most everyone, and accessible to a large number of programmers. i used to think this meant it was best in some absolute sense by virtue of matching up with the innate language faculty of humans. but the more i learn things like haskell and J, or just think about the way mathematical symbolism works, the more i think it’s not necessarily true. meaning other approaches which seem less natural at first might actually turn out to be much easier to work with once you’ve gained familiarity with them. i think this also explains why something like haskell can have such avid (and productive) devotees, while at the same time feeling inaccessible and not worth the effort to so many others.
Kurt Milam
@kurtmilam
Jul 24 2017 07:10
@JAForbes interesting discussion! I'm largely in agreement with you here. In the current projects I'm working on, a few things have grown clearer for me:
  1. Extracting some slice of logic from a pipeline and giving it a name is of questionable value if that slice of logic is specialized such that it's likely to only ever be used once in your application.
  2. 'Correct' names grow long fast. By 'correct', I mean names that accurately describe an operation or value. I think what often happens with naming is that the developer chooses a short name that's a poor (often even misleading) description of the thing (value or operation) being named. When that happens, any developers touching the code still have to evaluate what the code is actually doing (rather than being lazy and relying on the names) if they want to be confident in any changes they make to it, and if that code is only used once in an application, there's some context-switching involved if they have to leave the call site to evaluate the logic associated with the name rather than evaluating it in situ.
  3. Prematurely slicing things up into small, named parts can obscure patterns that could be abstracted and reused.
Kurt Milam
@kurtmilam
Jul 24 2017 07:25
I think premature slicing and naming offers an inaccurate view of the situation. The developer looks at the sliced up complex function and feels good because, "hey, this function is only 4 lines long with a few calls to other named functions, so it's going to be easy to get an overview of, digest and work with." But all of the complexity is still there -- it's just hidden (unnecessarily if only ever used once) behind names that are often misleading.
Artūras Zakrevskis
@AZakrevskis_twitter
Jul 24 2017 12:38

Hello,

I'm trying to switch

const getPlacesByIds1 = places => ids => 
  ids.map(id => places[id]).filter(x => x);

To something more like ramdajs, and finally I figured a way to get rid of places and ids.
Howeber the further I go the more code becomes hard to reason about.
Is it just because I am not used to it, or its a bit of overengineering? Is there a better way?

Ramda js repl full example

Artūras Zakrevskis
@AZakrevskis_twitter
Jul 24 2017 13:55
Added a 5 way:
https://goo.gl/FRDikc
Jonah
@jonahx
Jul 24 2017 14:06
@kurtmilam another key factor in whether a slice and name is helpful or noisy (aside from the name choice itself) is whether it labels a concept that is in fact a concept you’re already using mentally (and that others are likely using, if you’re on a team) or whether it labels a mere chunk of code. the latter typically being undesirable. and i’d even argue that not naming an active mental concept is almost always a mistake, since then you are constantly mapping some verbose representation back onto the concept you’re using – a for loop to represent a “map” being a quintessential example of that
Jonas Windey
@jonaswindey
Jul 24 2017 15:51
does anyone have a way to avoid sending the object to each prop() call in this example: https://goo.gl/HJNYb2 ?
Bijoy Thomas
@bijoythomas
Jul 24 2017 15:58
const mapProduct = o(flip(assoc('code'))({}), prop('ProductCode'))
Jonas Windey
@jonaswindey
Jul 24 2017 16:04
that will get pretty long for a big object
I need to transform 20 props
Jonas Windey
@jonaswindey
Jul 24 2017 16:15
and some props need transformation so I can't use evolve
Brad Compton (he/him)
@Bradcomp
Jul 24 2017 16:19
I would think some combination of evolve with the renameKeys function of the cookbook might help
Bijoy Thomas
@bijoythomas
Jul 24 2017 16:21
you can use toPairs to get your key& value pairs, filter/map your keys and values and then fromPairs the result
Brad Compton (he/him)
@Bradcomp
Jul 24 2017 16:28
Alternately, you could use converge with a bunch of functions of type Object -> Object and then unapply(mergeAll) as your converging function.
Jonas Windey
@jonaswindey
Jul 24 2017 16:31
ok, since some props have different transformations to apply I'll just send the object each time
Bijoy Thomas
@bijoythomas
Jul 24 2017 16:34
@Bradcomp that's a neat idea .. the different converging functions will make the intended transformations more obvious
Viktor Dzundza
@captainkovalsky
Jul 24 2017 16:35
Hi guys, are there some articles why most,
Why most is better than rxjs?
Jonas Windey
@jonaswindey
Jul 24 2017 16:51
@Bradcomp: in that case I'll still have to send the objects to each function with Object -> Object, no?
Brad Compton (he/him)
@Bradcomp
Jul 24 2017 17:15
converge will pass your parameters to each of the converging functions
Rick Medina
@rickmed
Jul 24 2017 17:21
@AZakrevskis_twitter it is subjective, depends on your and your team familiarity. Functions without explicit arguments are called "point free". I think most here would agree than being point free just for the sake of it is not a desirable property, at least not for just educational purposes.
Joey Figaro
@joeyfigaro
Jul 24 2017 17:57
Hey all. I’ve got a silly question. Trying conditional assignments…
const env = R.when(
    R.isEmpty(R.__),
    R.identity('development')
)(process.env.NODE_ENV);

// env = undefined
const env = R.when(
    R.isEmpty(process.env.NODE_ENV),
    R.identity('development')
);

// env = func f1
…actually, think I just answered my own question about the undefined bit. Technically isn’t empty, so. How can I execute that when so that env isn’t a function when I log it?
Kurt Milam
@kurtmilam
Jul 24 2017 18:05
@joeyfigaro Have you tried isNil rather than isEmpty?
Joey Figaro
@joeyfigaro
Jul 24 2017 18:06
@kurtmilam Hayo. I have not - checking that out.
That did the trick. :sweat_smile:
Kurt Milam
@kurtmilam
Jul 24 2017 18:10
:thumbsup:
Bravi
@Bravilogy
Jul 24 2017 18:39

hello. I have a function that returns a Future:

const login = params => new Future...

and I have the following function:

const application = 
   Future.of(curryN(2, compose(login, merge)))
      .ap(readConfig)
      .ap(getFormDetails);

so basically both - readConfig and getFormDetails return Futures, then once everything is resolved, two objects returned from each get merged and passed down to login function. Now the problem is that I end up with Future { Future }. One way to solve this would be to add something like
.chain(identity)after the last .ap but I don't like the look of it :D any other recommendations?

Austin Wei
@awei01
Jul 24 2017 18:52
@kurtmilam thx for the info, i'll try it out.
Rick Medina
@rickmed
Jul 24 2017 19:05
@Bravilogy not sure if I'm getting it but...
const application = 
   Future.of(curry(merge))
      .ap(readConfig)
      .ap(getFormDetails)
      .chain(login);
Bravi
@Bravilogy
Jul 24 2017 20:03
@rickmed silly me... I have no idea why I religiously wanted to use compose there :D thanks!