Hacker News new | ask | show | jobs
by mercurial 4151 days ago
I like the Pattern thing. However, it seems to me that you're going to quickly run into trouble if you need to even vaguely emulate shell scripting. Shell utilities live and die by their options. It's unfortunate Haskell supports neither named arguments nor default values. Which means that in order to emulate options, you would need to pass records to your "shell" utility, which, on top of being cumbersome, forces you to prefix every option in a way unique to your utility, since you cannot have two records with the same fields in the same namespace...
3 comments

It's going to be difficult for something like this to match the ease of shell scripting. For instance, typing `cd "foo"` is significantly more painful than `cd foo`. (Maybe this could be fixed by forking or adding to ghci so that when you hit space after the function name it automatically puts the quotes in for you and places the cursor between them.) Options are certainly important, and as others have said, they can be emulated with records. Is the syntax going to just as convenient as shell scripting? No. But that's not the point. The point is that shell scripting is massively painful in a lot of other ways where Haskell blows it away. So the task is to find a happy medium that gets fairly close to the convenience of shell scripting while still giving us the power of Haskell.

There are a number of potential approaches for coming close to the ease of shell scripting. One is options records as others have mentioned. For defaults you can have a Default instance (no, that's not boilerplate because you would have had to specify the defaults somewhere anyway). Then there is plenty of room for infix operator combinators to make it easier to change individual options. A second option could be to put options into a string that would get parsed into a record. You could use patterns similar to those used in existing command line argument processors like optparse-applicative. Or, if you don't like that, then maybe a quasiquote could give more power.

Do these things require some boilerplate? Yes. We know that is going to be required since Haskell wasn't designed for the convenience that shells were designed for. But that's fine in this case because the potential benefits are huge.

That's not the issue.

The issue is that, if you want to simulate both "grep" and "grep -r", you need to different functions, or you need to have your "grep" function accept a record of parameters.

I'm not good enough haskell programmer, but there is possible solution (records as you mentioned).

    import Prelude hiding ((-))

    data Grep = Grep {isRecursive :: Bool, maxCount :: Maybe Int} --etc
        deriving (Show)
    grep = Grep False Nothing
    --short pseudonim
    r :: Grep -> Grep
    r command = command{isRecursive = True}
    m :: Int -> Grep -> Grep
    m num command = command{maxCount = Just num}
    
    (-) :: a -> (a -> a) -> a
    (-) command flag = flag command
    ourGrep = grep -m 50 -r 
    
    main = print ourGrep -- > Grep {isRecursive = True, maxCount = Just 50}
    
    --then we should write monad which execute that data
Yes, that's exactly what I wouldn't want to type. Also, your record is going to blow up in the likely case another utility uses a recursive flag, because of Haskell's pervasive namespacing problems.
It probably wouldn't be too hard to do something like

    grep "foo"
      & "recursive" <~ True
      & "maxCount"  <~ 100
in a typesafe way. It just probably wouldn't be worth the complexity. It also probably couldn't be a straight `IO` action then, which was a design constraint of Gabriel's.
Actually, you can do `grep -r` by just combining `grep` and `lstree`. Here's an example:

    example = do
        file <- lstree "some/dir"
        True <- liftIO (testfile file)
        grep "Some pattern" (input file)
This is an example of how most of Bash's option heavy ecosystem is an outgrowth of Bash's limitation as a language (individual commands accumulate flags to work around functionality difficult to implement within the Host language). I think having a decent host language decreases the need for so many configuration knobs for every command.
You're right to some extent, but I think many of these 'knobs' have a good reason to exist (--dry-run, -a, -z for rsync for instance) and cannot be usefully, or at all, replaced by more composability. And attempting to implement support for them will run against the limitations of Haskell's syntax.

Something like OCaml would be better suited, since polymorphic variants, named and default arguments give a lot more flexibility, though the fact that shell commands happily return different outputs depending on their options would still be an issue.

Unix has

     find . -exec grep $pattern {} \;

but `grep -r` is easier to write.
I saw those as regular parser that returns text but not just grep wrapper. Yes It has function named 'grep', but its not grep wrapper. It looks like `lstree` could be combined with `grep` function for emulate `grep -r`.
Take lstree then. How would you give it an option giving the kind of ordering you want?
I guess you wouldn't, you'd have a sort function afterwards.

...for Unix' insistence on composability, the shell tools are often unnecessarily monolithic, probably because that's the only sane way if the only type you have in interconnect is `string`.

Sure, but consider the common case of wanting the most recent file/directory in a directory. This would be something like "ls -t|head -n 1". It's pretty convenient, AND your ls function still returns only file and directory names, no additional information.

Here, you'd need to either parametrize the return type of ls to get simple strings (which is what you want most of time) or additional metadata, or alternatively to have different ls commands.

If your arguments aren't exclusive, you can pass a list of algebraic data. If they are exclusive, you can construct a type for them.
> If your arguments aren't exclusive, you can pass a list of algebraic data.

Which you need to prefix to avoid clashes.

> If they are exclusive, you can construct a type for them.

Which is going to end up being a record, which:

- is awkward to build (compared to just giving options to a command or arguments to a function)

- will most likely need to be an instance of Default

- which needs to have its fields prefixed to avoid clashes

Starts to sound like an awful amount of boilerplate.

> prefixes, default instances

Also bad setters ("record {field = val}" looks nice but useless, as not a function).

I hope the developers of GHC also clearly see the problem and someday will be engaged in it. Then the language becomes much more expressive.

Is that task exists somewhere in roadmap?

There have been probably 15 different proposals for this throughout the years. They never make enough momentum to go through. While record pain points seem like a huge deal, in practice they're not sufficiently bad to motivate changes in the language in the face of the various tradeoffs that would have to be made.

Today, typeclasses and lenses cover 99% of "the record problem" as far as I've experienced.

There has been quite a bit of discussion on ghc-devs about fixing records. The most promising approach seems to here:

http://nikita-volkov.github.io/record/

Prefixes are a problem, no arguing about that. But...

> is awkward to build (compared to just giving options to a command or arguments to a function)

Idiomatic bash: apt-get install package_name

Idiomátic haskell: apt_get $ AptInstall package_name

What is so awkward about that?

First, it doesn't let you install multiple packages in one go (or let you install a specific package version). Secondly, it doesn't support apt-get options like -m, -d -n...

You could extend your argument structure for this, but then you need to specify every argument all the time, or have the user modify a default value. This is definitely awkward compared to straight shell.