These are chat archives for ramda/ramda

9th
Oct 2017
Brad Compton (he/him)
@Bradcomp
Oct 09 2017 00:38
I've said before, but I'm definitely with @CrossEye on this one. I think it's easier to have smaller functions that do less, and then build those into composite functions that do more. You can define the atomic operations on your data, and then build glue functions that can operate on those atoms, or else on the structures that our data is composed of.
In practice, I've found that keeping things small and separate leads to easier comprehension during code review, and easier debugging. It's quicker for me to pinpoint the problem spots when each function can be understood at a glance.
Kurt Milam
@kurtmilam
Oct 09 2017 01:19
It's certainly a question of degree, @Bradcomp . What would your optimal version of the solution in question look like if you knew none of the parts you abstracted away likely to be called anywhere else in the application?
Kurt Milam
@kurtmilam
Oct 09 2017 01:31
I think most experienced developers, myself included, prefer functions that are focused and short, but I am also certain that it's possible to go overboard in service of that goal, eventually reaching a point at which the marginal costs of atomization outweigh the marginal benefits thereof.
Scott Sauyet
@CrossEye
Oct 09 2017 01:35
That's quite certainly true. To me, the main point is about readability. I want to be able to grasp quickly what a function does. And it that the depends on further abstractions, I'm happy to name those abstractions for my local situation, and not let such one-off functions escape. As with so many things, it's a matter of finding a happy balance. I certainly sometimes create fairly long pipelines, but try to do so only if each step is entirely clear to me.
I'm actually a big fan of John Degoes' essay, Descriptive Variable Names: A Code Smell, but I find I'm interested in it mostly when creating utility functions, not typical business-oriented code.
As I said, I offered my solution because yours didn't fit my personal aesthetic, but I certainly wouldn't expect everyones' tastes to match mine.
Scott Sauyet
@CrossEye
Oct 09 2017 02:29
Ouch: "And if that then depends..."
Robert Mennell
@skatcat31
Oct 09 2017 02:32
@JAForbes the afformentioned MAthematica joke
// f of g composition prior art from Mathematics
const Mathematica = enter +> the
Mathematica(sandMan) // enter(the(sandMan))
arian‮
@arian-swydo
Oct 09 2017 08:08
Hey @kurtmilam
I wanted to ask you for some generic feedback
I made this lens projection: https://goo.gl/v59aak
do you think the API of this is good, and can it be written more clearly?
Kurt Milam
@kurtmilam
Oct 09 2017 08:58
@CrossEye I agree that readability is an important concern, if not the only one, and I'm skeptical that doubling the number of words, doubling the number of symbols, introducing three new function definitions / names and adding 60% more LOC leads to a net improvement in readability, especially when multiplied over a larger application. Here are the two solutions next to each other:
// monolithic solution
const getSteamappVdfLaunch = ({steamPlatform, arch}) =>
  o(either(find(both(pathEq(['config', 'oslist'], steamPlatform),
                     pathEq(['config', 'osarch'], arch))),
           head),
    values)
//  'chunked' / 'atomized' solution
const orFirst = pred => list => defaultTo(head(list), find(pred, list))
const rightPlatform = pathEq(['config', 'oslist'])
const rightArch = arch => cfg => arch === path(['config', 'osarch'], cfg)

const getSteamappVdfLaunch = ( {steamPlatform, arch, launchConfig} ) => pipe(
  values,
  orFirst(both(rightPlatform(steamPlatform), rightArch(arch)))
)(launchConfig)
Kurt Milam
@kurtmilam
Oct 09 2017 09:03
Now, I would have been more likely to split this function up had I not been writing in a functional, declarative style, but I find that writing code in a functional, declarative style already improves readability over a more imperative style. I find either ( find ( both ( pathEq, pathEq ) ), head ) very readable, already. It almost sounds like a natural sentence in the English language.
Kurt Milam
@kurtmilam
Oct 09 2017 09:09
Comparing just the main function definitions, we see that yours contains 12 words as opposed to the 16 in mine. So, you've added 26 words, 4 LOC and 3 function definitions in order to save 4 words in the main function definition. I'm skeptical that this is a question de gustibus in this case.
Aside: I've had John Degoes' essay open in another tab since Friday, I think, and I had initially typed de gustibus non est disputandum in one of my first responses to you, but I didn't go with that precisely because I feel strongly that the whole discussion doesn't boil down solely to a question de gustibus or personal aesthetics.
Kurt Milam
@kurtmilam
Oct 09 2017 09:32
I've mostly compared the lengths of the solutions, but I believe there are other costs to premature abstraction. In my experience, premature abstraction has, at times, obscured patterns that make interesting targets for reusable abstractions. I find that to be the case in this example, as well.
Kurt Milam
@kurtmilam
Oct 09 2017 09:38
First, I see an interesting, likely reusable pattern that could be extracted to a function I would probably name defaultOp:
const defaultOp = 
  tryFn => defaultFn => x =>
  { const tried = tryFn( x )
    return(
      isNil( tried )
        ? defaultFn( x )
        : tried
    )
  }
This is superior to having orFirst depend on defaultTo, since the defaultFn will only be evaluated when it's needed, whereas it will always be evaluated inside of defaultTo.
Barney Carroll
@barneycarroll
Oct 09 2017 09:46
Interesting subject @kurtmilam, usually I err strongly towards your preferences: point-free, fluent code with a minimum of references. Destructure everything, introduce complexity where and when it is relevant, etc. But I think this is very difficult for a lot of people.
I can't help but notice that I'm extremely liberal with sentences in conversation - I'll use loads of punctuation, compound propositions, etc. A lot of people find it incredibly hard to follow, even as I find it difficult to split up my point into smaller sentences with less involved grammar.
I certainly remember a time when I felt it was essential to break things up - even arbitrarily - in order to rationalise the logic. I remember 7 years ago taking fluent code someone else had written and breaking out operations into discrete functions from right to left.
Kurt Milam
@kurtmilam
Oct 09 2017 09:50
Second, I see an interesting and likely reusable pattern that could be extracted into a function I might name something like matchSpec. I won't provide an implementation right now, but I will show how it would work. Rather than using both( pathEq, pathEq) as the predicate in our 'find', we'd use this function, passing a spec object into it that would then be applied to a candidate object, returning a boolean that tells us whether the candidate passes or matches the spec. In our case, we'd call the function like this:
const configSpec =
  { config: { oslist: equals( steamPlatform )
            , osarch: equals( arch )
            }
  }
find( matchSpec( configSpec ) )
I am convinced that these are powerful and very likely reusable abstractions without seeing the rest of the original asker's codebase, whereas I'm much less convinced about the reusability of rightPlatform or rightArch.
Kurt Milam
@kurtmilam
Oct 09 2017 09:59
@barneycarroll thanks for chiming in! Fascinating to consider the possible corollaries between an individual's use of natural language and the style of code they're likely to write. I also tend to speak (and write) in long, flowing sentences with plenty of parentheticals :D
I also used to chop code up religiously, and I continued doing that as I first started learning to program in a functional and declarative style. It took a year of experience or so for me to realize that what works in a procedural, object oriented and/or imperative coding style may not be as suitable for code written in a functional, declarative style.
I also aim for a point free style with minimum references, but I acknowledge that there are cases where achieving that style can be detrimental to readability, so I'm not a purist :)
Kurt Milam
@kurtmilam
Oct 09 2017 10:14
I hope I have shown convincingly with these two examples how premature abstraction can obscure more powerful and reusable candidates for abstraction. I have found this to be the case in practice, as well.
Barney Carroll
@barneycarroll
Oct 09 2017 10:21
I agree with that too, premature separation of concerns can be a barrier to seeing where those concerns might work together for greater gain
But that's a higher order benefit. Kyle Simpson got quite religious about 'many named functions' on the basis of his experience teaching people Javascript from scratch.
I think the first time I noticed how much I disliked attempts at enforcing that kind of hyper encapsulation was roughly 5 years ago when I worked at a company with absurdly opinionated linting
No more than 5 levels of indentation, no more than 2 function invocations per scope, etc etc
Barney Carroll
@barneycarroll
Oct 09 2017 10:27
Realise I'm just rambling here - the point I wanted to stress was that I've met many people for whom points and named references are essential to reading comprehension
At a level that's lower and more essential than 'elegance of abstraction'
Kurt Milam
@kurtmilam
Oct 09 2017 10:27
Here's a rewrite of my solution using the reusable abstractions defined above:
const getSteamappVdfLaunch = ({steamPlatform, arch}) => 
  o(defaultOp(find(matchSpec({config: {oslist: equals(steamPlatform),
                                       osarch: equals(arch)}})),
              head),
    values)
@barneycarroll Yeah, one thing I tend to wonder about is whether those people who currently need points could learn to live and thrive without them with practice.
And how would things look for a new developer who was taught from scratch is a (more) point-free style?
Kurt Milam
@kurtmilam
Oct 09 2017 10:33
I can say this: I would prefer to work with a team of developers who were comfortable reading and writing point free code, but I'm also aware that this style is not idiomatic to JavaScript (yet). I'm doing my best to try to help spread it :D
Barney Carroll
@barneycarroll
Oct 09 2017 10:34
By and wide I agree, I'd like to see more of that
I'd be interested in an attempt at formal criteria for good abstraction
On one hand you could argue that hindsight over years of writing bad abstractions has taught me that they're pointless unless the abstraction reduces complexity, but I'm not sure how much of that first hand experience was necessary to getting to this point :)
Kurt Milam
@kurtmilam
Oct 09 2017 10:47
Believe me, I have asked around and looked for papers on the subject, but most answers I have gotten are sort of hand-wavy 'do what feels right to you' and I haven't come across any papers on the subject, especially applied to a declarative, functional programming style.
I do think it's something that could be quantified, however.
I've talked about two of the costs of premature abstraction, but there is (at least) a third cost, and that is the cost that comes with naming things.
Barney Carroll
@barneycarroll
Oct 09 2017 10:49
I mean, there's easily verifiable things like "does it have call multiple call sites?", but I think that wouldn't work for the Getify school of thought whereby repetition is irrelevant, it's compartmentalisation
Kurt Milam
@kurtmilam
Oct 09 2017 10:50
Especially in naming things that only have a single call site.
I think that cost can be high and long-lasting, but it may be the most difficult to quantify.
Barney Carroll
@barneycarroll
Oct 09 2017 10:51
Well yes, but TBH I find that to be unnecessary separation of concerns, a name for no benefit
🤔
Kurt Milam
@kurtmilam
Oct 09 2017 10:52
Sure, that point wasn't directed specifically at you. Rather, it was directed at those who argue for naming things that are only called once.
Barney Carroll
@barneycarroll
Oct 09 2017 10:52
Right
Kurt Milam
@kurtmilam
Oct 09 2017 10:53
I have some general rules I try to follow: I usually only name something if it will be called from more than one site. If a thing is becoming too complex and feels like it needs to be broken into pieces, I try as hard as I can to find reusable patterns that I can abstract away.
The cost of naming a reusable piece of logic is much lower than the cost of naming a piece of logic that will not be reused, because the other developers working with you are more likely to see that reusable logic in different parts of the code base, so they're more likely to be familiar with what it's doing.
I don't mind asking a developer to learn what matchSpec or defaultOp does, since they're bits of logic that will likely be useful in multiple parts of the application.
But I don't feel comfortable asking the developer to learn what a single-use function does. They're likely to forget and have to refer back to the function definition every time they come in contact with it.
And this is a point on naming: a name that I find descriptive and declarative may not be interpreted in the same way by my colleagues. It may not even be interpreted the same way by me a week or two after I write it.
You can try to tackle the problem with strict naming conventions and code reviews, but I see that as attacking the symptom and not the cause.
Kurt Milam
@kurtmilam
Oct 09 2017 11:00
If I come up with my own naming convention in a team, and it's not adopted by every team member, the convention may become useless as other developers use the same convention to denote different functionality, or use different conventions to denote the same functionality.
So I think that, unless you're working alone or in an environment with strong, shared naming guidelines and enforcement thereof, most developers will, in most cases, have to scan the body of the named function if it's only called once to ensure that they understand what it's doing. Using the example function here, that would mean that a developer might need to read all 8 lines of code and 38 words in the chunked solution in order to feel comfortable that they know what is going on under the hood.
When that is the case, I think it is safe to say that the chunked solution is less readable than the monolithic one, especially when compared to the latest version I posted using defaultOp and matchSpec.
Barney Carroll
@barneycarroll
Oct 09 2017 11:04
Yeah, Hungarian notation is fun like that
So I think there's 2 arguments for high level encapsulation, which are almost the same thing
The first is as a method of writing
You are juggling too many moving parts mentally to know where to put the next foot
So you break out
And depending on your priorities you can write the disembodied function safe from the distractions of the higher scope you were in, or you can carry on sorting out those domain concerns and leave the particulars of this recondite operation till later
Barney Carroll
@barneycarroll
Oct 09 2017 11:09
The second is in reading, where like you say the case is a lot more dubious
Kurt Milam
@kurtmilam
Oct 09 2017 11:12
That's a good point, and I do sometimes break things out 'prematurely' during early development of some new feature, but knowing that my first instincts aren't always my best instincts in that area, I'll usually try to bring the constituent parts back into a whole in order to look for better targets for abstraction.
Barney Carroll
@barneycarroll
Oct 09 2017 11:13
Yeah me too
Kurt Milam
@kurtmilam
Oct 09 2017 11:14
I also think that identifying good targets for abstraction is a skill that can be improved with time and experience.
Barney Carroll
@barneycarroll
Oct 09 2017 11:14
Yeah, and to a degree making mistakes and living with their consequences is the method
Kurt Milam
@kurtmilam
Oct 09 2017 11:16
:+1: I have been there, looking at code with a bunch of names I came up with but no longer remember exactly what they mean, realizing that I had obscured useful targets for abstraction. My arguments are definitely based on experience.
There are only two difficult things in computer science: cache invalidation, naming things and off by one errors :D
Stephan Meijer
@smeijer
Oct 09 2017 11:28
is there already a method that executes a function if it is defined? Something like R.call, but so it doesn't throw when first argument is not a method.
Kurt Milam
@kurtmilam
Oct 09 2017 11:29
I don't think there's anything built in.
Stephan Meijer
@smeijer
Oct 09 2017 11:34

that's unfortunate, because it's a common pattern :confused:

if (typeof cb === 'function') {
  cb(/* error, result*/)
}

This one does look nicer:

R.callback(cb, /* error, result */);
Adam Szaraniec
@mimol91
Oct 09 2017 12:03
is there some kind of roadmap , what have to be done before releasing ramda as v1.0.0?
Adam Szaraniec
@mimol91
Oct 09 2017 12:14

I've noticed some non-intuitive behaviour of flip
https://goo.gl/pWw1Ex
It says:

Returns a new function much like the supplied one, except that the first two arguments' order is reversed.

And seems that for function which have only 2 arguments it does not work ?

Kurt Milam
@kurtmilam
Oct 09 2017 12:16
@mimol91 Here you have a function that takes a single argument and returns a second function that takes another argument. I think the function has to accept 2 arguments in order for R.flip to work.
Adam Szaraniec
@mimol91
Oct 09 2017 12:16
Right!
Kurt Milam
@kurtmilam
Oct 09 2017 12:17
I usually use my own version of flip that works with manually curried functions, as well. Let me dig it out.
Adam Szaraniec
@mimol91
Oct 09 2017 12:17
I've started to try learn haskell, and now my mind is lost :)
Kurt Milam
@kurtmilam
Oct 09 2017 12:17
The other option is to have the function expect two arguments and use R.curry on it if you want it to be curried, as well.
Me too. I'm enjoying it (learning Haskell) so far. I went through the first 3 weeks of this free online Haskell course from the University of Glasgow pretty quickly, but have taken a break while family is visiting from out of the country.
I've found the course pretty easy, so far, but that may be largely due to the fact that I have been reading up on Haskell on the side quite a lot over the past few months, trying to learn what I hope are 'best practices' for programming in a functional style in any language.
Adam Szaraniec
@mimol91
Oct 09 2017 12:22
oh, thanks. I will look on it. for now I m just learning form learnyouahaskell, but I am rather on beginning so, its easy =)
Kurt Milam
@kurtmilam
Oct 09 2017 12:23
Good stuff. The next book I will buy will probably be the Haskell Book. I have heard lots of good things about it.
Adam Szaraniec
@mimol91
Oct 09 2017 12:24
There is a notation which confuse me
compareWithHundred :: (Num a, Ord a) => a -> Ordering
It just mean that a can be a type of Num or Ord right?
Usually there were only one type before =>
Kurt Milam
@kurtmilam
Oct 09 2017 12:31
@mimol91 I believe that means the argument has to be a member of Ord and Number. See the original, full chapter here, where this is mentioned.
As for flip, this should work as long as you always pass in one argument at a time to the flipped function:
const flip = fn => x => y => fn( y )( x )
Adam Szaraniec
@mimol91
Oct 09 2017 13:00
I thought also about this solution, -> easy and simple
Scott Sauyet
@CrossEye
Oct 09 2017 13:29
@kurtmilam: where else in this codebase are you using the defaultOp abstraction? ISTM that you are doing the same sort of premature abstraction you're rejecting. That's actually the type of abstraction I don't like: purely speculative ones based on a single example. I don't think of rightPlatform as that same type of abstraction. It would be encased in a small module, and used nowhere else... unless I found myself needed that same behavior elsewhere. Then I have a ready-made implementation to actually abstract.
Here I find named functions quite a bit easier to work with. I used to be all about point-free implementations, but I came to find that no one else could read them, and soon enough I couldn't either. Now my test is simply: which one is easier to read? Of course that's subjective, but it's still a straightforward test.
And if we disagree about which one is easier to read, then we're going to choose different solutions. On a team, you need to reach some sort of consensus about this.
Scott Sauyet
@CrossEye
Oct 09 2017 13:37
orFirst is the function I would think most likely abstracted, and I wrote it with that intent. But instead of immediately abstracting it to a utility library, I leave it in place, seeing if the need arises again.
This message was deleted
Scott Sauyet
@CrossEye
Oct 09 2017 13:44
rightPlatform and rightArch I might find in other nearby code, but I wouldn't expect to push them too far from their roots. That's fine with me. The names are clear enough in context. (I would probably actually use rightArchitecture if I wasn't starting from existing code that used arch, but that's minor.)
Meanwhile, when you do add an abstraction to your five-line function in defaultOp, you add nine more lines of code. I don't think this is an issue, but you seem to find more lines, more symbols, more characters to be problematic.
Kurt Milam
@kurtmilam
Oct 09 2017 13:55
I wouldn't extract any of that out unless I knew it would be useful somewhere else in the code base. I would simply make note of the useful abstractions and keep them in a mental tool chest for later use.
I have used something similar to matchSpec on multiple occasions, usually either for validation or classification of data, so it's something I already use in other projects.
I'm not as certain about defaultOp, since I haven't needed it before. But it seems to occupy an interesting space between defaultTo and ifElse, so it could come in handy.
Scott Sauyet
@CrossEye
Oct 09 2017 13:58
And note that I don't feel that pulling out named functions is a universal good. I argued yesterday for inlining someone else's functions. But I make that call based on readability.
Kurt Milam
@kurtmilam
Oct 09 2017 13:58
And yes, I find that multiplying the number of functions by 4, the number of lines by 1.6 and the number of words by two is problematic if all of the new code will only be called from one site.
I also mentioned above that I'm not a point-free purist. It's something I'll use when it fits, but I won't jump through hoops to ensure that every function I write is point free.
I also regularly advise devs on this channel and elsewhere to eschew point free purity, opting instead for readability, first.
Scott Sauyet
@CrossEye
Oct 09 2017 14:01
You see, I don't find those arguments at all persuasive. Why not reduce all variables to single-character, remove all non-functional whitespace if a major goal is brevity?
Kurt Milam
@kurtmilam
Oct 09 2017 14:01
The goal isn't brevity. It is expressiveness.
My goal is to write composed functions that read like natural English.
Scott Sauyet
@CrossEye
Oct 09 2017 14:02
Yes, I agree. But I don't think we agree on what makes something more expressive...
Kurt Milam
@kurtmilam
Oct 09 2017 14:02
Where something needs a name, I think the name should be meaningful.
Scott Sauyet
@CrossEye
Oct 09 2017 14:03
I have a hard time reading the code you posed, partly because I don't think o is expressive, and the LISP indentation confuses me more than it helps.
Kurt Milam
@kurtmilam
Oct 09 2017 14:03
OK, but that's down to formatting and the choice to use o rather than pipe. That's why I reformatted the code to make it less LISPy.
I have no problem using pipe instead (although I'd usually use one of my own making) or using a compose that works like o.
What I'd like to see is a compose operator added to the language. I'd prefer that greatly to o.
krzysztofpniak
@krzysztofpniak
Oct 09 2017 14:05
Anyone knows how to solve this using ramda, point free style and complexity close to O(n)? https://tinyurl.com/y7ffyhm2
Scott Sauyet
@CrossEye
Oct 09 2017 14:07
I would love a compose operator. I choose compose over pipe only when the arguments all fit on one line.
Kurt Milam
@kurtmilam
Oct 09 2017 14:08
I like o for when I only have two function arguments, although I know it's less expressive than compose.
Scott Sauyet
@CrossEye
Oct 09 2017 14:08
But these are not the only concern. I tend to find that I get lost in deeply nested code. It's not that I can't read it, only that it takes far longer than I would prefer.
Kurt Milam
@kurtmilam
Oct 09 2017 14:09
I don't usually end up with what I would consider to be deeply nested code, either.
I'm not sure whether you consider my (reformatted) example to be deeply nested.
Scott Sauyet
@CrossEye
Oct 09 2017 14:10
All in the eye of the beholder, I guess.
Kurt Milam
@kurtmilam
Oct 09 2017 14:10
And while I don't like deeply nested code, I find it more annoying to jump out of a function body to read the body of another function that is only called once.
Scott Sauyet
@CrossEye
Oct 09 2017 14:11
Well, this is more than I usually prefer: o(either(find(both(pathEq(...
It's an interesting debate. I don't at all find your preference wrong, or in any way problematic. In fact, I used to work that way. But I prefer a very different style now.
Kurt Milam
@kurtmilam
Oct 09 2017 14:15
It seems your limit is nesting 4 functions deep (as opposed to my 5)? pipe(orFirst(both(rightPlatform(...
Scott Sauyet
@CrossEye
Oct 09 2017 14:15
(And that does not mean anything like, "Well, you'll grow out of it", if it sounded that way. I honestly believe that this is a matter of taste, and tastes vary from person to person, and change over time for everyone.)
Kurt Milam
@kurtmilam
Oct 09 2017 14:16
Thanks for being gracious, by the way.
I don't mean any of this personally :)
I do think it's an interesting and important topic, and I'd be ecstatic to learn that someone had studied the questions at hand, but if they have, I haven't been able to locate their findings.
Scott Sauyet
@CrossEye
Oct 09 2017 14:18
Thank you too. An interesting discussion.
Kurt Milam
@kurtmilam
Oct 09 2017 14:19
:+1:
Scott Sauyet
@CrossEye
Oct 09 2017 14:23
@krzysztofpniak: looking now.
Can you guarantee that children will always appear after their parents in the initial list?
Michael Rosata
@mrosata
Oct 09 2017 14:27
I tried to read the code posted yesterday @kurtmilam for the steam arch snippet. I wasn't really following the conversation so I didn't know what the snippet was for, I was actually drawn in by your style of formatting the code (I thought it was very interesting, appeared to have some thought put into it) and wanted to see if it was something that could be beneficial or that I might adopt myself. I tried to decode the purpose of the code without actually knowing the purpose (because oftentimes that's what looking at old code feels like anyways). I personally was unable to figure it out within a few minutes.
granted I was using the Ramda repl and so didn't have highlighted bracket matching on the cursor, so that made it tricky.
Kurt Milam
@kurtmilam
Oct 09 2017 14:29
@mrosata that's interesting input, thanks for sharing it. Were you working with the original (more LISPy, more parens) version or the more recent one?
Michael Rosata
@mrosata
Oct 09 2017 14:30
@kurtmilam this one
const archPathEq = pathEq( ['config', 'osarch'] )

const getSteamappVdfLaunch =
  ( { steamPlatform, arch } ) =>
    o( either( find( both( pathEq( [ 'config', 'oslist' ] )
                                 ( steamPlatform )
                         ) 
                         ( either( archPathEq( undefined ) )
                                 ( archPathEq( arch ) )
                         )
                   )
             )
             ( head )
     )
     ( values )
Kurt Milam
@kurtmilam
Oct 09 2017 14:31
If with the original, it may be helpful to note that opening and closing parens always line up vertically if they're on separate lines. I usually find it reasonably easy to see which closing parens matches an opening parens by simply scanning up or down vertically.
Michael Rosata
@mrosata
Oct 09 2017 14:31
now looking at it again, I guess I shouldn't need bracket highlighting
Kurt Milam
@kurtmilam
Oct 09 2017 14:32
Right, that's what I was hoping to say. It's definitely not a common coding style for a couple of reasons, but I think it may be reasonably easy to grok once you understand a couple of the principles I follow.
I really try for vertical alignment of matching symbols, and I call most functions as if they expected one argument at a time (for functions where that's possible - it isn't for pipe and compose, for instance).
I generally (but not always), put each argument to a function on its own line.
I'll sometimes skip that for super simple calls or where there's very little nesting, etc.
Michael Rosata
@mrosata
Oct 09 2017 14:34
yea, yesterday the brackets were confusing for me, today I saw it as soon as I sent the post probably because I verbalized what threw me off and I don't have the tunnel vision of looking at it already
Kurt Milam
@kurtmilam
Oct 09 2017 14:36
:+1: I know the style is not for everyone. It's sort of a mix of the npm team's coding guidelines plus supplying one argument at a time to each function and manually currying lots of functions.
Michael Rosata
@mrosata
Oct 09 2017 14:36
I've been doing this lately
const someFunction = (
  {
    arg1,
    arg2
  }
) => {
  // . . .
}
but it can feel overkill in some instances (basically anytime the function isn't a default export it feels too much)
Kurt Milam
@kurtmilam
Oct 09 2017 14:37
I have done something similar to that as well, example:
export default
  ( { data
    , lens = []
    , label = form.lensToName( lens )
    , placeholder = label
    , value = U.view( lens )
                    ( data )
    , schema
    , isValid = U.atom( L.set( lens )( true )( {} ) )
    , validClass = U.ifte( U.view( lens )
                                 ( isValid )
                         )
                         ( 'is-valid' )
                         ( 'is-invalid' )
    , ...props
    }
  ) =>
    <label className={ U.cns( 'labeled-text-input-row', validClass ) }>
      <div>{ label }</div>
      <div>
        <TextInput { ...{ value
                        , ...props
                        }
                   }
        />
      </div>
    </label>
Although I'm not 100% sure about that style :D
A whole lot happening before you even get to the body of the function :D
Michael Rosata
@mrosata
Oct 09 2017 14:38
I never got too much into the comma first style. I tried it when npm released their style guide a few years ago.
krzysztofpniak
@krzysztofpniak
Oct 09 2017 14:38
@CrossEye If its required I can sort it earlier.
Michael Rosata
@mrosata
Oct 09 2017 14:39
Will that example run without parenthesis around the jsx?
Kurt Milam
@kurtmilam
Oct 09 2017 14:39
I was never into it until I looked for a way to more quickly get an overview of the organization of nested functions written in a functional style.
Scott Sauyet
@CrossEye
Oct 09 2017 14:40
@krzysztofpniak: I doubt it's necessary. But it might make the difference between one pass and two passes.
Kurt Milam
@kurtmilam
Oct 09 2017 14:40
That function works as posted, no parens needed.
Michael Rosata
@mrosata
Oct 09 2017 14:40
sometimes I find situations where comma first works well for me (callback functions for sure)
Kurt Milam
@kurtmilam
Oct 09 2017 14:43
I never liked comma first until I saw someone vertically lining up the commas with the parens or braces that surrounded them, and vertically lining up all of the words separated by commas.
  [ one
  , two
  , three
  ]
I find that vertical alingment to be pretty clear and easy to follow.
krzysztofpniak
@krzysztofpniak
Oct 09 2017 14:44
@CrossEye Cool
Michael Rosata
@mrosata
Oct 09 2017 14:44
it doesn't look bad in the large example above, and I do like the haskelly style alignment (although I don't use it in js)
Kurt Milam
@kurtmilam
Oct 09 2017 14:45
Yeah, that's the other influence. I figure Haskellers probably know something about optimal formatting for functional code.
Michael Rosata
@mrosata
Oct 09 2017 14:45
I also didn't had no idea that arguments could reference other arguments inside defaults
I would have thought it would have to be pulled out of the params
export default (_props) => {
  const { data  // loses alignment in destructuring
    , lens = []
    , label = form.lensToName( lens )
    , placeholder = label
    , value = U.view( lens )
                    ( data )
    , schema
    , isValid = U.atom( L.set( lens )( true )( {} ) )
    , validClass = U.ifte( U.view( lens )
                                 ( isValid )
                         )
                         ( 'is-valid' )
                         ( 'is-invalid' )
    , ...props
    } = _props
return (
    <label className={ U.cns( 'labeled-text-input-row', validClass ) }>
      <div>{ label }</div>
      <div>
        <TextInput { ...{ value
                        , ...props
                        }
                   }
        />
      </div>
    </label>
)
Kurt Milam
@kurtmilam
Oct 09 2017 14:47
That's all working code. You can see a quick working example using that component here. Code is here
Wait, what is this last example you shared?
If you want to rewrite the component using destructuring inside the body, you'll need to adjust the alignment manually. If that's generated code, again, you'd need to manually fix the alignment.
Unfortunately, I have not found much in the way of built in support for my style.
Michael Rosata
@mrosata
Oct 09 2017 14:52
lol, I don't suspect that you would :)
Joey Figaro
@joeyfigaro
Oct 09 2017 14:52
Hey folks. Anyone have any tips on how I might deal with using or as the predicate for ifElse here? https://goo.gl/ERMFAW
Kurt Milam
@kurtmilam
Oct 09 2017 14:52
It makes me sad :D :(
Michael Rosata
@mrosata
Oct 09 2017 14:52
wait a year or so
I think we're moving towards a "you write your way, I write mine" in the same codebase type world
Kurt Milam
@kurtmilam
Oct 09 2017 14:53
Yeah, maybe if I continue to evangelize it enough, it'll catch on and we'll start to see some support.
@joeyfigaro this works, but it's not how I'd solve the problem.
Michael Rosata
@mrosata
Oct 09 2017 14:55
something like prettier can take anyones code and format it to a standard on git commit, so it's not a stretch that another library could codemod the reverse when updating the local repo. As you say, you'd need some custom support
Kurt Milam
@kurtmilam
Oct 09 2017 14:56
That would be awesome. I've been working on private projects recently, but I will begin working with a team soon, and I'm sure my style won't fly with the team :)
Joey Figaro
@joeyfigaro
Oct 09 2017 14:57
@kurtmilam how might you go about it? Don’t need a code sample; just curious to hear thought process.
Kurt Milam
@kurtmilam
Oct 09 2017 14:57
@joeyfigaro the first 3 arguments to ifElse should be functions, and a fourth argument must be supplied in order to fire it.
Michael Rosata
@mrosata
Oct 09 2017 14:58
I pasted your code into my IDE and it made it so angry that it tore up my license
Kurt Milam
@kurtmilam
Oct 09 2017 14:59
@joeyfigaro I believe this does what you're looking for.
I tested it quickly, and it seems to work.
@mrosata :D so far, I haven't had any IDEs tear up my licenses or say nasty things to me...
But, as I'm sure you can imagine, I have to fiddle with linting rules to make the linters happy.
Michael Rosata
@mrosata
Oct 09 2017 15:01
@kurtmilam it's not all your fault, me and my IDE have been sleeping in separate rooms lately anyways
Joey Figaro
@joeyfigaro
Oct 09 2017 15:01
@kurtmilam ahhh, of course. thanks for clearing that up.
Kurt Milam
@kurtmilam
Oct 09 2017 15:02
@joeyfigaro And I'd probably change things a little more, but that points you in the right direction. The main thing is to understand the types of the arguments ifElse expects.
You're welcome!
@mrosata :D what IDE are you using?
I have mostly been using WebStorm for the past few years, but I've been taking VS Code for a spin lately.
Joey Figaro
@joeyfigaro
Oct 09 2017 15:03
Yeah, I need to learn how to read the signatures in the docs: (*… → Boolean) → (*… → *) → (*… → *) → (*… → *)
Michael Rosata
@mrosata
Oct 09 2017 15:03
I use webstorm too
Kurt Milam
@kurtmilam
Oct 09 2017 15:04
@joeyfigaro this might be helpful.
Joey Figaro
@joeyfigaro
Oct 09 2017 15:05
Beautiful. :raised_hands:
Kurt Milam
@kurtmilam
Oct 09 2017 15:05
Also, don't overlook the little EXPAND PARAMETERS link included with each function in the docs.
Michael Rosata
@mrosata
Oct 09 2017 15:06
@kurtmilam I enjoy Webstorm, I use vim for small things but always Webstorm for important work. How is VS Code? I have heard good things about it here and there
Kurt Milam
@kurtmilam
Oct 09 2017 15:08
Unfortunately, I don't have enough experience with VS Code yet to offer a useful evaluation. It seems nice, and they put the plugins front and center, meaning they encourage you to search for and install plugins that you think may be useful for you.
Have to run. WebStorm says dinner is served.
Joey Figaro
@joeyfigaro
Oct 09 2017 15:11
VS code is pretty solid. I haven’t gone as far as some folks in turning it into a full-blown IDE, but agree with the plugins front and center bit. Great ecosystem/plugin availability, and it’s responsive. Linting and indexing are quick.
Linting in the latest sublime text has been painful.
Michael Rosata
@mrosata
Oct 09 2017 15:13
@kurtmilam lol
@joeyfigaro I might check it out, I downloaded it and set a reminder to install it over the weekend
I tried Atom for awhile, I enjoyed it, I configured it and then ran back to Webstorm
I'm a weak man
Joey Figaro
@joeyfigaro
Oct 09 2017 15:14
hahaha
I’ve yet to find an editor I can lint with that is as fast as atom. Using eslint was instantaneous. Indexing was horrid, and filesize restrictions were bad.
Haven’t really tried vim/shell editors in earnest, though. Can’t afford to lose productivity while I learn it.
Barney Carroll
@barneycarroll
Oct 09 2017 15:39
Atom may be fast but it's power hungry, my last laptop couldn't run it and my (admittedly very inefficient) Rails app in dev mode concurrently
But then Sublime can't syntax highlight multiline argument destructuring because apparently it's syntax buffer works on a line by line basis
WRT commas, I believe we will soon have a full house of trailing commas for all JS literals, at which point commas last makes more sense for me
Scott Sauyet
@CrossEye
Oct 09 2017 15:53
@krzysztofpniak This is not as functional as I'd like, but it's a start:
const makeTree = pipe(
  reduce(
    ({index, nodes}, item) => {
      const parent = defaultTo(nodes, index[item.parentId])
      const node = dissoc('parentId', assoc('children', [], item))
      parent.children.push(node)
      return {
        index: assoc(item.id, node, index),
        nodes
      }
    }, 
    {index: {}, nodes: {children: []}}
  ),
  path(['nodes', 'children'])
)
Joey Figaro
@joeyfigaro
Oct 09 2017 16:02
@barneycarroll I had to stop using Atom because of the memory requirements. Machine only has 8gb.
Barney Carroll
@barneycarroll
Oct 09 2017 16:03
Yeah same :/
Scott Sauyet
@CrossEye
Oct 09 2017 16:48

@krzysztofpniak: This is probably better:

const makeTree = items => {
  const hierarchy = reduce(
    (index, item) => item.parentId in index 
      ? assoc(item.id, [], assoc(item.parentId, append(item.id, index[item.parentId]), index))
      : assoc(item.id, [], index)
    , {}, 
    items
  ) //=> E.g. {"1":[2],"2":[3],"3":[],"4":[]} 
  const index = map(head, groupBy(prop('id'), items)) //=> E.g. {"!": <item1>, "2": <item2>, ...}
  const makeNode = id => dissoc('parentId', assoc('children',  map(makeNode, hierarchy[id]), index[id]))

  return map(makeNode, pluck('id', filter(item => item.parentId == null, items)))
}

You can see this in the REPL.

It does involve several passes through the data, and the use of groupBy here seems a bit hacky, but I think it's reasonable.

Also those nested assocs aren't pretty. I might refactor into compose/pipe.
arian‮
@arian-swydo
Oct 09 2017 19:07
hey guys, question about flip
it doesn't seem to work with unary function, or maybe I'm misunderstanding:
Philipp Wille
@Yord
Oct 09 2017 20:19
const list = [1, 2, 3, 4]
const oo = o(o)(o)(o)(o)

o(tail)(tail)(list) //=> [3, 4]
oo(head)(tail)(tail)(list) //=> 3
:D
arian‮
@arian-swydo
Oct 09 2017 20:30
dude
that is awesome
I don't understand it, but it's awesome
Philipp Wille
@Yord
Oct 09 2017 20:33
it is super weird :D
I have no intuition what those combination result in
arian‮
@arian-swydo
Oct 09 2017 20:35
haha, I think with enough practice these combinators will make intuitive sense
(the reason I'm practicing)
Philipp Wille
@Yord
Oct 09 2017 20:35
I definitely hope so :D
arian‮
@arian-swydo
Oct 09 2017 20:36
You've got something there though I think, to flatten out the structure
I'm already past the point of getting anything passed through code review
definitely using that one, thanks @Yord
Philipp Wille
@Yord
Oct 09 2017 20:40
@arian-swydo ;)
Kurt Milam
@kurtmilam
Oct 09 2017 20:43
const oo = o(o(o))(o) <- also works
o(o)(o)(o)(o) evaluates to o(o(o))(o)
Kurt Milam
@kurtmilam
Oct 09 2017 20:49
Here are the steps:
o(o)(o)(o) // evaluates to:
o(o(o))
o(o(o))(o) // not evaluated, waiting for another argument
o(o(o))(o)(head) // evaluates to:
o(o)(o(head))
o(o)(o(head))(tail) // evaluates to:
o(o(head)(tail))
o(o(head)(tail))(tail) // not evaluated - waiting for the list argument
I sometimes like to think of o as being short for 'onion', since each application sort of peels off a layer when they're nested. That's not where the name comes from, though.
arian‮
@arian-swydo
Oct 09 2017 20:54
dude, I was just trying to figure this out haha, thanks so much
Kurt Milam
@kurtmilam
Oct 09 2017 20:55
:+1: :D
Kurt Milam
@kurtmilam
Oct 09 2017 21:06
To finish the evaluation by applying it to list:
o(o(head)(tail))(tail)([1,2,3,4]) //->
o(head)(tail)([2,3,4]) //->
head([3,4]) //->
3
arian‮
@arian-swydo
Oct 09 2017 21:16
@kurtmilam , seems wrong but does this make sense: // o1(o2)(o3)(o4)(o5)(x) => o5(o4(o2(o3(x))))
Kurt Milam
@kurtmilam
Oct 09 2017 21:29
I don't think that's right. It's more like o3(o4)(o5(x)), I believe.
arian‮
@arian-swydo
Oct 09 2017 21:31
ah yes it should be a recursion
need some obvious way to solve this haha
for me intuitvely this seems to make more sense '01(02(03)(04(05)))' than '01(02(03)(04))(05)'
but i can't figure out why
which can't be right since it would expect another function instead of a list
Kurt Milam
@kurtmilam
Oct 09 2017 21:35
o3(o4)(o5(x))(y) //-> o4(o5(x)(y))
o4(o5(x)(y))(z) // doesn't evaluate
o4(o5(x)(y))(z)(a) // ->
o5(x)(y)(z(a)) // ->
x(y(z(a)))
I think that's right.
but it's past my bed time :D
arian‮
@arian-swydo
Oct 09 2017 21:36
good night
Kurt Milam
@kurtmilam
Oct 09 2017 21:36
:+1: