Partly preamble. A week ago, I began to learn Scala - so how about Nim (and it's disappointing) read almost nothing (in my native language - especially). Written for Nim module nimfp, implements very similar functional paradigm that is in the Scala - and this is well documented in Scala. Therefore, a book about Scala, telling about flatMap, foldLeft, flatten, forall and so on is very useful. I should add that in Scala, all the methods used in the code implemented for all collections, and are also supported for strings. And it is very convenient.
At the end of the study I did a little comparison of two languages in the functional plane of working with strings. Maybe Nim should develop in this direction? This is not a criticism, but just a suggestion for the future.
import sequtils
import strutils,future
echo "test".len # "test".length
echo "hello world".capitalize # "hello world".capitalize
echo "hello world".toUpper # "hello world".toUpperCase
echo "HELLO WORLD".toLower # "HELLO WORLD".toLowerCase
echo " test".strip # " test ".trim
echo "hello world".split(" ") # "hello world".split(" ")
echo "hello world".startsWith("hel") # "hello world".startsWith("hel")
echo "hello world".endsWith("rld") # "hello world".endsWith("rld")
echo "test".replace('e','o') # "test".replace('e','o')
echo "test".replace("e","o") # "test".replace("e","o")
echo "hello world".replaceWord("hello","") # "hello world".replaceAll("hello","") или "hello world".replaceAllLiterally("hello","")
echo "test".count('t') # "test".count(_ == 't')
echo "*".repeat(10) # "*" * 10
echo "test".find('t') # "test".indexOf('t')
echo "test".find('t',1) # "test".indexOf('t',1)
echo "test".rfind('t') # "test".lastIndexOf('t')
echo "test".rfind('t',1) # "test".lastIndexOf('t',1)
echo "hello world".substr(0,5) # "hello world".substring(0,5)
echo "test".contains('t') # "test".contains('t') or "test".exists(_ == 't')
echo "hello"[^1] # "hello".last == o
echo "hello"[0] # "hello".head == h
echo "hello"[0..^2] # "hello".init == hell
echo "hello"[1..^1] # "hello".tail == ello
echo "hello world"[1..3] # "hello world".slice(1,4) == ell
echo "hello"[0..2] # "hello".take(3) == hel
echo "hello"[2..^1] # "hello".drop(2) == llo
var str = "weekend"
str.removeSuffix("end");echo str # "weekend".stripSuffix("end") == week
str.delete(0, 1); echo str # ???
Some things in Scala performed functions can be simulated on Nim for loop or list comprehensions:
for s in "test": s.echo # "test".foreach(println _) -> t e s t
# code examples on Nim similar to expression on Scala: (1 to 10).map("*" * _).foreach(println _)
for s in toSeq(1..10).map(x => repeat("*",x)): echo s
for s in toSeq(1..10).mapIt(repeat("*",it)): echo s
for s in {1..10}.mapIt(repeat("*",it)): echo s
for s in {1..10}: echo repeat("*",s)
for s in 1..10: "*".repeat(s).echo
echo lc[x|(x <- "abc123"), char].anyIt(it.isDigit) # "abc123".exists(_.isDigit)
echo lc[x|(x <- "test", x == 't'), char] # "test".filter(_ == 't') -> tt
echo lc[x|(x <- "test"), char].filter(x => x == 't') # "test".filter(_ == 't') -> tt
echo lc[x|(x <- "tttt"), char].all(x => x == 't') # "tttttt".forall(_ == 't') -> true
echo lc[$x.toUpper| (x <- "test"), string] # "test".map(_.toString.toUpperCase) -> Vector(T, E, S, T)
echo lc[x.toUpper| (x <- "test"), char]
echo lc[x|(x <- "qqwweerrtyy"), char].deduplicate.join # "qqwweerrtyy".distinct -> qwerty
However, Scala's a lot of things, the implementation of which is not in Nim
# "test".count(_.isLower) -> 4
# "scala".matches("s(c|k)al\\w") -> true
# "test".filter(_ == 't') -> tt
# "tttttt".forall(_ == 't') -> true
# "test".map(_.toString.toUpperCase) -> Vector(T, E, S, T)
# "test".flatMap(x=>x.toString.toUpperCase) - TEST
# "test".intersect("west") -> tes
# "abcde".diff("efghi") -> abcd
# "hello".union(" world") -> hello world
# "test".replaceFirst("t","f") -> fest
# "test".zipWithIndex -> Vector((t,0), (e,1),(s,2), (t,3))
# "hello world".reverse -> dlrow olleh
# "test".splitAt(2) -> (te,st)
# "0xffff".stripPrefix("0x") -> ffff
# "hello".takeRight(2) -> lo
# "hello".dropRight(2) -> hel
# "eeeetest".dropWhile(_ =='e') -> test
# "eeeetest".takeWhile(_ =='e') -> eeee
# "Harry".patch(1,"ung",2) -> Hungry
# "test".partition(_ == 't') -> (tt,es)
# "hello world".find(_.toInt > 110) -> Option[Char] = Some(o)
# "test".indices -> Range(0, 1, 2, 3)
# "abcd".permutations.foreach(println _) -> abcd,abdc,acbd ... dcba
# "zyxwvutsrqponmlkjihgfedcba".sortWith((x,y)=>x < y) -> abcdefghijklmnopqrstuvwxyz
# "zyxwvutsrqponzMlkjiHgfedcba".sortWith((x,y:Char)=>x.isUpper) -> HMzyxwvutsrqponzlkjigfedcba
# "zyxwvutsrqponmlkjihgfedcba".sortBy((x:Char) => x.toInt) -> abcdefghijklmnopqrstuvwxyz
# "HwEoLrLldO".groupBy { _.isUpper } -> Map(false -> world, true -> HELLO)
# "test".toCharArray -> Array(t, e, s, t)
# "test".getBytes() -> Array(116, 101, 115, 116)
# "test".foldLeft(0)((x:Int,y:Char)=>x+ y.toInt) -> 448 (the same: 't'.toInt + 'e'.toInt +'s'.toInt + 't'.toInt)
# "1234567890".grouped(2).foreach(println _) -> 12 34 56 78 90
# "1234567890" sliding 2 foreach println -> 12 23 34 45 56 67 78 89 90
PS: Some examples, of course, artificial and have no practical significance. They just show possible.
PSS:Sorry for my English, spoken I do not know at all, and I can only read the technical documentation.
Yes I really do like scala, and I think scala has a lot in the functional programming domain, that I would like to have in Nim, too.
Here is a pattern from scala that I like to use to avoid the creation of intermediate variables in object scope, when in practice only the result is interesting. It also tells the reader of the code, that the variables are also really only used in that context, and never accessed from outside. It is also very useful, when I want to use statements (Unit methods) in expressions.
def foo: (Int, Float) = {
val a = 17
val b = 4.0f
(a,b)
}
// foo: (Int, Float)
val (a1,b1) = foo
// a1: Int = 17
// b1: Float = 4.0
val (a2,b2) = {
val a = 17
val b = 4.0f
(a,b)
}
// a2: Int = 17
// b2: Float = 4.0
I tried to do the same in Nim, and this is how far I came with it.
proc foo(): tuple[a:int, b:float] =
let a = 17
let b = 4.0
(a, b)
var (a1,b1) = foo()
echo a1, b1
var (a2, b2) = (block:
let a = 17
let b = 4.0
(a,b)
)
first of all, it is weired, that I need to use the block keyword here, and the () pair, otherwise the nim compiler would not accept that construct. It would really be nice, if that would not be necessary. But even then it still doesn't work, this is an example where the nim compiler generates non compileable C code.
Just for the curious people, even gnu c has an extension to do this: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs
Another comparision, is by how generic the algorithms are desigend. For example in scala I can use almost all of the methods shown by you also on other collections, unless it really is a string specific operation. In Nim there is sequtils that implements a few nice algorithms, but they are not generic at all, they work on the seq type. If I implement my own collection type, for example the double linked list, I can't use the methods from sequtils, because they are not generic on the colloction type. In scala I can inherit from Traversable, implement one abstract method, and my collection is already a very powerful collection.
class MyIntCollection(val data : Array[Int]) extends collection.Traversable[Int] {
def foreach[U](f: Int => U) {
data foreach f
}
}
object Main {
def main(args: Array[String]) {
val mc = new MyIntCollection(Array(1,2,3,7,6,5,3))
println( mc.size ) // 7
println( mc.count(_ == 3) ) // 2
println( mc.last ) // 3
println( mc.head ) // 1
println( mc.init ) // List(1,2,3,7,6,5)
println( mc.tail ) // List(2,3,7,6,5,3)
println( mc.slice(1,4) ) // List(2,3,7)
println( mc take 3 ) // List(1,2,3)
println( mc drop 2 ) // List(3,7,6,5,3)
}
}
Note that methods, that create another collection, like tail and drop, simply create a List, not a MyIntCollection. This is due to the fact, that Traversable is the most simple abstract collection type in scala, and it is not possible with just a foreach call. All shown methods are implemented using foreach, even size, but when a faster version can be implemented, MyIntCollection can override the size method. Of course there are other traits that you can inherit from, in order to have more methods available.
In Nim there is sequtils and strutils, all have their methods implemented based on that one collection type. These methods can't be used on other collection types. In c++, there is algorithm in the standard library, which is ugly to use, becaues of its begin(collection), end(collection) iterator parameters, but it get's the job done, and works generically on iterators of all collections, not just std::vector
The stdlib is mostly as generic as the lack of a working concept implementation allows for. No surprises here and thanks for the update, I lived under a rock the last ten years and don't know about C++'s iterators and D's slices and Clojure's transfinite enumerations.
Having said that, I still think flatMap et al are cargo cult programming, especially how Scala does it (map can have as many side-effects as a for loop since the lambda can have side-effects). So how many procs and iterators do you need to replace for + if + break? Turns out, dozens. It's like CISC vs RISC designs.
And once you use map and filter everywhere you will complain the code ain't optimized well. Here we go, let's steal Haskell's deforestation then. Oh but the papers about it only apply to Haskell, so let's turn it into a research project... See the problem? It never stops creating work. It's exactly this approach that Nim has followed for far too long and why Nim is not at version 1 for 5 years already. I don't even know if it's really that bad if my sort algorithm doesn't work with your precious arrogant re-implementation of a seq. You re-invented seq, you can re-invent sort too then. Seems fair to me, all things considered.
Hi,
Let me add my view on this topic. I created many topics here (for example this and this) to improve the functions in sequtils. The reception was very positive, and after some discussion I created some pull requests which were accepted. Because these are heavily used functions, we need to make small steps not to break everybody's code.
Peter
Garry_galler you can also put all of the helpers you coded into a package and publish it with Nimble.
Nim is not Scala but it is very flexible and you can expand it with packages that don't require the core to change or everyone to adopt your perspective.
@Araq I am sorry I stepped on your foot. Keep on with your work on this great language, and thank you for very good community support. I mean it without any sarcasm.
And you are completely right, Nim needs to focus on the things that are truly important, and don't loose focus in the details.
I think the most important features of Nim are:
C compatability, meaning calling C and being able to write in a c way, that has the same performance as c. macros/metaprogramming
These two features are the reason I even started using Nim. Without them I would never had a reason to start programming in Nim.
you can also put all of the helpers you coded into a package and publish it with Nimble
Some things, owing to their prevalence, in my opinion have to be part of standard library of language. For example, the string methods\functions toCharArray, getBytes, reverse are in many languages. And programmers don't need to invent anything. Could not a little to expand the standard string module? If everyone starts writing the helpers modules for elementary things, it won't be good style for language.
Simple example. In Nim there are modules for work with almost all built-in collections, however Tuple was for some reason ignored. There is an informal tuples package which it meets a lack. But here comes some inconsistency naming functions.
For type sequences (sequtils module) there are foldl and foldr functions and they have syntax @[1,2,3].foldl(a+b).
For type tuple (tuples module) there is fold, where syntax already another: (1,2,3).(`+`).
For type List (nimfp module) there is foldLeft: asList (1,2,3).foldLeft (0, (x, y) => x + y).
That we have: three types, three names of the same procedure, three syntaxes of use.
Araq, if you are a long time programmed in Scala, then what's wrong with the unification of methods in Scala?
PS: All this is just the opinion of a beginner (though who had to deal with a dozen languages and, therefore, has a tendency to compare them :-)). I really like Nim, and I am very grateful to you for your work, and I also like all who use it, I want it to be even more powerful and more convenient.
Some things, owing to their prevalence, in my opinion have to be part of standard library of language. For example, the string methodsfunctions toCharArray, getBytes, reverse are in many languages.
The problem is, when is enough... I can think of a dozen things i like to see in Nim. But they are all not essential. At some point there needs to be a lock down on functions, so a 1.0 version can be reached. Because as long as 1.0 is never reached, there will be no broad pickup of the language. The helper functions you mentioned above can easily be added to the language on later versions.
Unification of methodes across external modules is a different topic then the helper functions. And i do agree, the more unified calls are the more easy for people to learn. But external modules like nimfp, tuples are maintained by other users. So that kind of invalidates your point. Araq has no say over how other developers create there own modules. If those modules get merged into the Nim standard library, then sure... they must be updated to reflect the default behavior.
Remember Garry, a lot of the constructive criticism that you point out can be addressed after 1.0 release.
The main focus now needs to be:
All that ties back into a 1.0 release. The basic idea was ( from what i read ) to have 1.0 is 2015! We are already half past 2016 and from the open bugs etc it will be surprising if there is a 1.0 release in 2016.
The issue goes beyond 1.0. As Nim keeps stalling with no official 1.0 release, GoLang, rust etc keep build up there communities. Trust me, its no fun being a "obscure" language when a few big one's run with all the glory. Because this directly influences the developer base. Do not think that Araq will be able to keep doing Nim core development for the next 10, 20 years. At some point more people will be needed for core development. And that goes back to having a large community.
Qua features Nim is very much 1.0. So the focus is bug fixing and 1.0 release. Simple as that.
Everybody has an opinion on what features Nim needs. What we need is people who can either (A) implement those features themselves, or (B) pay someone to do it. ;)
</hypocrisy>