This week I tried to acquaint myself with the basic theory of Haskell, which is my first experience with functional programming.
In functional programming, functions accept arguments and return an answer based off of the original argument. Arguments are not modified within the function and variables stay the same.
Haskell is described as lazy – no data is shown until it is absolutely necessary to do so, for example when a function has finished calculating a result. It is also statically-typed (that is to say, Haskell is aware of variable types at compile-time) and uses type inference where type is not explicitly stated.
When writing Haskell functions, the programmer does not need to worry about adding a control flow structure in the same way as they may have to with procedural languages. An example of this is given below. To my understanding, the C# factorial function executes a multiplication function x + 1 times (with a 0-starting for loop) and store to a variable after each iteration whereas Haskell will perform the entire calculation at once.
As well as this, I learned some useful information about comprehension and use of functions.
Infix functions in Haskell are functions whose calls can be embedded between parameters. Suppose the divide function were to be called with an aim of dividing 90 by 10. This can be done in one of two ways – div 90 10 or 90 `div` 10. This is to allow easier comprehension of functions where functions may be complex.
‘ may be used at the end of a function name to denote a strict version of the function or a slightly modified version of a function, which is a naming convention I was not previously aware of.
List comprehensions in Haskell are fairly compact and consist of an output function before a pipe and a predicate after the pipe. There may also be conditions after the predicate.
[ x | x <- [1..100], x `mod` 5 == 0]
In this example, the output function returns x. The input set is a list of all numbers between 1-100 with x drawn from the list. The predicate is that returned numbers will only be those who are divisible by 5 with no remainder (or who return 0 when modulus 5 is called on them)
Tuples are a data structure commonly found in Haskell. In my mind, they work quite similarly to vectors. Each element must have a set size and corresponding variables within elements must be of the same data type. For example [[1 , 2], [3, 4, 5], [5, 6]] is a valid list but [(1 , 2), (3, 4, 5), (5, 6)] is an invalid tuple, and [(1, “One”), (1, “Two”)] is valid, where [(1, 1), (2, “Two”)] is not. Functions do exist to create tuples from lists, but only if they meet the conditions for a valid tuple.
Also this week I looked at some entries of the 365 Tidal Patterns blog. I found that many of them won’t compile which I initially thought may be due to sample names, but it seems to be syntactic errors which at this point I’m unsure of how to fix.
Because of this I had to use patterns at random instead of following my initial plan.
This week, through playing around with these patterns I learned several things.
|+|speed controls playback speed of individual arguments in sound. In the example below, the playback speed of hh and cp would change to 2 and 3 respectively. This can be a way of altering pitch.
d1 $ sound “bd hh cp”
|+|speed “1 2 3”
|+|shape alters the distortion (and volume) of the block using a value between 0 and 1 (the default is 0). This can be used with different wave patterns already defined in Tidal, such as sinewave.
The “every” keyword is used to instruct the program to do something every x number of times. This works quite effectively with the rev keyword, which reverses the order of the samples, to make the music sound less repetitive.
d1 $ every 2 rev sound “bd sn:1 sn:2”
This example also shows how the colon is used to select a particular sample from the named sample folder. The numbering system is 0-indexed so the use of sn:1 will take the second sample in the sn folder.