Wrapping simple path strings in the framwork

38 views
Skip to first unread message

Henrik Härkönen

unread,
Feb 3, 2018, 4:12:22 AM2/3/18
to Lift
Hi everyone!

As I was making changes for the framework ticket #1919 (Asset root path should be configurable), I started feeling
some anxiety with the loose path strings here and there. I started to think that perhaps it would be better if they were wrapped
with some type and functionality for consistency, easy usage and also "findability". 

Finally the question from Tim Nelson, that should the configured root path be an empty string instead of "/", triggered me to
make this simple builder for paths that would be convenient and consistent to use, easy to read and find from code and also
nice to vend from the DI system in applications. At least I have many this kind of paths in use in my lift apps, so some sort of
handle for the app developer would be good too.

So, basically this is only for static paths, I know there's all kinds of general URL and URI builders, but I had something more
simple yet specialized in my mind.

Some examples from console:

Initializing an empty path (not that useful though :)

scala> LPath()
res0
: LPath.LPath =

Defining a path starting from root (the case in the ticket):

scala> LPath().fromRoot
res1
: LPath.LPath = /

scala> LPath().fromRoot.withFolders("myassets" :: "mysubdir" :: Nil).withFolder("anothersubdir")
res3: LPath.LPath = /
myassets/mysubdir/anothersubdir/


Complete path for some file resource perhaps:

scala> LPath().fromRoot.withFolders(List("myassets", "subdir")).withFile("abc.jpg")
res4
: LPath.LPath = /myassets/subdir/abc.jpg


So the LPath object will store the path components, which here are about root, folder objects and the file object. toString method will
render the path then how it was used as a string before. Joining of LPaths is supported with .join method or ++ operator, as long as 
one doesn't try to join a path with root component to another path, or any path to a path with file component, as they would be
ambiguous situations.

Now, defining for example the configured asset path and then having a component to use it as a base, would go like this:

In configuration we'd have it like this, instead of plain string object:

scala> val assetRoot = LPath().fromRoot.withFolder("assets")
assetRoot
: LPath.LPath = /assets/


Then usage by joining paths together with ++ operator:
scala> assetRoot ++ LPath().withFile("myimage.jpg")
res5
: LPath.LPath = /assets/myimage.jpg

Of course mostly with simple cases like this, assetRoot could be used directly:

scala> assetRoot.withFile("myimage.png")
res6
: LPath.LPath = /assets/myimage.png

Implementation is following build pattern, utilizing immutable case classes. This construct would remove the possibility for
confusion about which parts of string joining the "/" chars would be etc.

What do you think, could this be added to the framework and then use it in for example the #1919 ticket and possibly find out 
other uses as well?

Br,
Henrik.

Joe Barnes

unread,
Feb 5, 2018, 10:33:02 AM2/5/18
to lif...@googlegroups.com
Henrik,

Cool ideas! I'm always a fan of using typed data rather than loose strings. I'm 100% in favor of exploring how we can improve the types we use for paths in Lift.

You briefly mentioned that you know of URL/URI builders, but you want something more specific for our case. I certainly agree that URL/URI builders would be too general for this case. 

What type(s) do you have in mind here? Is LPath your only type? Do you intend for intermediate paths (directories) to be distinguishable from leaf paths (files don't have children)? Without honestly thinking it through, seems like this would be a nice distinction. 

I'll be interested to see what feedback you get otherwise regarding the API you're proposing. I'm a big fan of using operators when they make sense, but I don't get away with that a lot in the Lift code base :) Personally I'd shoot for just using an implicit conversion that makes slash an operator on string, much like sbt does for making paths. But again, that's just my love for using special characters over words when the special character is well-known in the context.

The final important thing to look into here is how much of the Lift API surface deals with paths (and thus could use the extra type safety), and how much does the typical application developer deal with these paths. In my experience, this is something you might tweak once in a while. When you tweak something as major as a path to assets, it's pretty fast and easy to figure out if you got it right. Most times the app is complete garbage if you got it wrong. If the application developer will rarely benefit from the added type safety, it's unlikely to be worth the trouble to add it to Lift. 

Joe



/**
 * Joe Barnes
 * Owner/Principal Consultant, joescii, llc
 */


--
--
Lift, the simply functional web framework: http://liftweb.net
Code: http://github.com/lift
Discussion: http://groups.google.com/group/liftweb
Stuck? Help us help you: https://www.assembla.com/wiki/show/liftweb/Posting_example_code

---
You received this message because you are subscribed to the Google Groups "Lift" group.
To unsubscribe from this group and stop receiving emails from it, send an email to liftweb+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Henrik Härkönen

unread,
Feb 5, 2018, 11:46:15 AM2/5/18
to Lift
Hi Joe!

Thanks for the feedback!  Replied inline:

 
What type(s) do you have in mind here? Is LPath your only type? Do you intend for intermediate paths (directories) to be distinguishable from leaf paths (files don't have children)? Without honestly thinking it through, seems like this would be a nice distinction. 


Currently, LPath is the only one, yes. It stores root as boolean, folder structure as vector of string and then the possible leaf (file) object as Option[String].

Originally, I had this idea that I'd use Algebraic Data Type for it. I'm kind of in love with ADTs, but I couldn't make that one fly so fast, so I decided to try with a simple version first to see how it would feel like to use as a "client". 

With ADT version there probably would need to be a LPath as sealed trait and then concrete types for root path, leaf path, combination of those perhaps, and then one for the rest of the cases. The neat thing about this would be that the ambiguous join situations could be prevented already in the compilation phase! This current implementation just throws with an informative message if the join operation is undesired. I don't like exceptions at all, but here I think it would be acceptable, as the usage is merely config related.

But, as you said, most likely these path handlings are more in the category "one-off jobs", so didn't want to spend too much time with it yet. :) 



The final important thing to look into here is how much of the Lift API surface deals with paths (and thus could use the extra type safety), and how much does the typical application developer deal with these paths. In my experience, this is something you might tweak once in a while. When you tweak something as major as a path to assets, it's pretty fast and easy to figure out if you got it right. Most times the app is complete garbage if you got it wrong. If the application developer will rarely benefit from the added type safety, it's unlikely to be worth the trouble to add it to Lift. 

 
Yes, I agree that  it might not be that big of a feature for an app developer, but on the other hand it would be nice to get even these "just right" on the first try. Just recently I was struggling with some path method on a Java project at work, and I just couldn't get it to work properly. After some good 15 minutes of debugging, I noticed that I just needed to add the "/" at the end of the path string, even while the method was supposed to be dealing with folders explicitly! 

-Henrik

Henrik Härkönen

unread,
Feb 16, 2018, 3:32:45 AM2/16/18
to Lift
More thoughts on this one? :)

(Would need to have some kind of consensus regarding the PR https://github.com/lift/framework/pull/1933 how to proceed)

-Henrik

Matt Farmer

unread,
Mar 3, 2018, 9:51:36 AM3/3/18
to Lift
Hey all,

We still don't have consensus here. I'm proposing we merge the PR as-is for now and revisit this later.

If I don't hear any objections in the next few days, I'll probably do this. :)

Antonio Salazar Cardozo

unread,
Mar 3, 2018, 1:11:05 PM3/3/18
to Lift
~ on adding new types to deal with paths… It seems like half the time I've seen it done,
things get really weird really fast. There are usually bunches of corner cases and such.
Moreover, Lift already deals with paths as lists in a bunch of places. If we do introduce a
typed way to build paths, I would like to explore how we might reuse it throughout the
codebase.

Agree on not blocking the PR on this discussion though.
Thanks,
Antonio
Reply all
Reply to author
Forward
0 new messages