All,
I want to derive the type expression for the function below in ML:
fun f x y z = y (x z)
Now I know typing the same would generate the type expression. But I wish to derive these values by hand.
Also, please mention the general steps to follow when deriving type expressions.
I'm going to try to do this in the most mechanical way possible, exactly as the implementation in most compilers would.
Let's break it down:
fun f x y z = y (x z)
This is basically sugar for:
val f = fn x => fn y => fn z => y (x z)
Let's add some meta-syntactic type variables (these are not real SML-types, just place holders for this example's sake):
val f : TX = fn (x : T2) => fn (y : T3) => fn (z : T4) => y (x z) : T5
OK, so we can start generating a system of constraints from this. T5 is the eventual return type of f. For the moment, we're going to just call the eventual type of this whole function "TX" - some fresh, unknown type variable.
So the thing that is going to be generating constraints in the example you've given is function application. It tells us about the types of things in the expression. In fact, it's the only information we have!
So what do the applications tell us?
Ignoring the type variables we assigned above, let's just look at the body of the function:
y (x z)
z is not applied to anything, so we're going to just look up what the type variable we assigned to it was earlier (T4) and use that as its type.
x is applied to z, but we don't know its return type yet, so let's generate a fresh type variable for that and use the type we assigned x (T2) earlier to create a constraint:
T2 = T4 -> T7
y is applied to the result of (x z), which we just called T7. Once again, we don't know the return type of y yet, so we'll just give it a fresh variable:
T3 = T7 -> T8
We also know that the return type of y is the return type for the whole body of the function, we we called "T5" earlier, so we add the constraint:
T5 = T8
For compactness, I'm going to kludge this a little and add a constraint for TX based on the fact that there are functions being returned by functions. This is derivable by exactly the same method, except it's a little more complex. Hopefully you can do this yourself as an exercise if you're not convinced that we would eventually end up with this constraint:
TX = T2 -> T3 -> T4 -> T5
Now we collect all the constraints:
val f : TX = fn (x : T2) => fn (y : T3) => fn (z : T4) => y (x z) : T5
TX = T2 -> T3 -> T4 -> T5
T2 = T4 -> T7
T3 = T7 -> T8
T5 = T8
We start to solve this system of equations by substituting left hand sides with right hand sides in the system of constraints, as well as in the original expression, starting from the last constraint and working our way to the top.
val f : TX = fn (x : T2) => fn (y : T3) => fn (z : T4) => y (x z) : T8
TX = T2 -> T3 -> T4 -> T8
T2 = T4 -> T7
T3 = T7 -> T8
val f : TX = fn (x : T2) => fn (y : T7 -> T8) => fn (z : T4) => y (x z) : T8
TX = T2 -> (T7 -> T8) -> T4 -> T8
T2 = T4 -> T7
val f : TX = fn (x : T4 -> T7) => fn (y : T7 -> T8) => fn (z : T4) => y (x z) : T8
TX = (T4 -> T7) -> (T7 -> T8) -> T4 -> T8
val f : (T4 -> T7) -> (T7 -> T8) -> T4 -> T8 = fn (x : T4 -> T7) => fn (y : T7 -> T8) => fn (z : T4) => y (x z) : T8
OK, so this looks horrible at the moment. We don't really need the whole body of the expression sitting around at the moment - it was just there to provide some clarity in the explanation. Basically in the symbol table we would have something like this:
val f : (T4 -> T7) -> (T7 -> T8) -> T4 -> T8
The last step is to generalise all the type variables that are left over into the more familiar polymorphic types that we know and love. Basically this is just a pass, replacing the first unbound type variable with 'a, the second with 'b and so on.
val f : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
Which I'm pretty sure you'll find is the type that your SML compiler will suggest for that term too. I did this by hand and from memory, so apologies if I've botched something somewhere :p
I found it difficult to find a good explanation of this inference and type constraint process. I used two books to learn it - 'Modern Compiler Implementation in ML' by Andrew Appel, and 'Types and Programming Languages' by Pierce. Neither one was independently completely illuminating for me, but between the two of them I figured it out.
To determine the type of something you need to look at every place where it is used. For example if you see val h = hd l, you know that l is a list (because hd takes a list as an argument) and you also know that the type of h is the type that l is a list of. So let's say the type of h is a and the type of l is a list (where a is a placeholder). Now if you see val h2 = h*2, you know that h and h2 are ints, because 2 is an int, you can multiply an int with another int and the result of multiplying two ints is an int. Since we previously said the type of h is a this means that a is int, so the type of l is int list.
So let's tackle your function:
Let's consider the expressions in the order in which they are evaluated: First you do x z, i.e. you apply x to z. That means x is a function, so it has the type a -> b. Since z is given as an argument to the function it has to have the type a. The type of x z is therefor b because that is the result type of x.
Now y is called with the result of x z. This means y is also a function and its argument type is the result type of x, which is b. So y has the type b -> c. Again the type of the expression y (x z) is therefor c because that is the result type of y.
Since those are all the expressions in the function, we cannot restrict the types any further and therefor the most general types for x, y and z are 'a -> 'b, 'b -> 'c and 'a respectively and the type of the whole expression is 'c.
This means the overall type of f is ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
For an explanation of how types are inferred programatically read about Hindley–Milner type inference.
Another way to explain type inference is that every (sub)-expression and every (sub)-pattern are assigned a type variable.
Then, each construct in the program has an equation relating those type variables that are relevant to that construct.
E.g., if the program contains f x
and 'a1 is the type variable for the f, and 'a2 the type variable for the x, and 'a3 is the type variable for "f x",
then the application results in the type equation:
'a1 = 'a2 -> 'a3
Then, type inference basically involves solving the set of type equations for a declaration. For ML this is done just by using unification, and it's pretty easy to do by hand.
Related
As the documentation on OCaml is sparse, i would appreciate if some one can explain the difference in different flavors of let usage.
I tried looking into https://dev.realworldocaml.org/toc.html, but there is no easy way to search in the website. Google search landed me to some articles, but did not get the exact explanation.
The basic form of let expressions is:
let p1 = e1
and p2 = e2
...
and pN = eN
in e
where N is at least 1. In this form, let expressions pattern matches the value that results from evaluating the RHS expressions against the LHS patterns, then evaluates the body with the new bindings defined by the LHS patterns in scope. For example,
let x, y = 1, 2 in
x + y
evaluates to 3.
When let has an operator name attached, it is the application of what is called a "let operator" or "binding operator" (to give you easier terms to search up). For example:
let+ x, y = 1, 2 in
x + y
desugars to (let+) (1, 2) (fun (x, y) -> x + y). (Similar to how one surrounds the operator + in parentheses, making it (+), to refer to its identifier, the identifier for the let operator let+, as it appears in a let expression, would be (let+).)
Finally, when a let binding has an operator name attached, all the and bindings must have operator names attached as well.
let* x = 1
and+ y = 2
and* z = 3 in
x + y + z
desugars to (let*) ((and+) 1 ((and*) 2 3)) (fun ((x, y), z) ->).
The following program is invalid and has no meaning because the let binding is being used as an operator, but the and binding is not:
let* x = 1
and y = 2 in
x + y
Binding operators are covered in the "language extensions" section of the OCaml documentation.
let () = e is merely the non-operator form of a pattern match, where () is the pattern that matches the only value of the unit type. The unit type is conventionally the type of expressions that don't evaluate to a meaningful value, but exist for side effects (e.g. print_endline "Hello world!"). Matching against () ensures that the expression has type (), catching partial application errors. The following typechecks:
let f x y =
print_endline x;
print_endline y
let () =
f "Hello" "World"
The following does not:
let f x y =
print_endline x;
print_endline y
let () =
f "Hello" (* Missing second argument, so expression has type string -> unit, not unit *)
Note that the binding operators are useful for conveniently using "monads" and "applicatives," so you may hear these words when learning about binding operators. However, binding operators are not inherently related to these concepts. All they do is desugar to the expressions that I describe above, and any other significance (such as relation to monads) results from how the operator was defined.
Consider the following code from the OCaml page on let operators.
let ( let* ) o f =
match o with
| None -> None
| Some x -> f x
let return x = Some x
If we create a very simply map:
module M = Map.Make (Int)
let m = M.(empty |> add 1 4 |> add 2 3 |> add 3 7)
If we wanted to write a function that takes a map and two keys and adds the values at those keys, returning int option, we might write:
let add_values m k1 k2 =
match M.find_opt k1 m with
| None -> None
| Some v1 ->
match M.find_opt k2 m with
| None -> None
| Some v2 ->
Some (v1 + v2)
Now, of course there are multiple ways of defining this. We could:
let add_values m k1 k2 =
match (M.find_opt k1 m, M.find_opt k2 m) with
| (None, _) | (_, None) -> None
| (Some v1, Some v2) -> Some (v1 + v2)
Or take advantage of exceptions:
let add_values m k1 k2 =
try
Some (M.find k1 m + M.find k2 m)
with
| Not_found -> None
Let operators let us write:
let add_values m k1 k2 =
let* v1 = M.find_opt k1 m in
let* v2 = M.find_opt k2 m in
return (v1 + v2)
Haskell replaces for loops over iteratable objects with map :: (a -> b) -> [a] -> [b] or
fmap :: (a -> b) -> f a -> f b. (This question isn't limited to Haskell, I'm just using the syntax here.)
Is there something similar that replaces a while loop, like
wmap :: ([a] -> b) -> [a] -> ([b] -> Bool) -> [b]?
This function returns a list of b.
The first argument is a function that takes a list and computes a value that will end up in the list returned by wmap (so it's a very specific kind of while loop).
The second argument is the list that we use as our starting point.
The third argument is a function that evaluates the stoping criteria.
And as a functor,
wfmap :: (f a -> b) -> f a -> (f b -> Bool) -> f b
For example, a Jacobi solver would look like this (with b now the same type as a):
jacobi :: ([a] -> [a]) -> [a] -> ([a] -> Bool) -> [a]
What I'm looking for isn't really pure. wmap could have values that mutate internally, but only exist inside the function. It also has nondeterministic runtime, if it terminates at all.
In the case of a Gauss-Seidel solver, there would be no return value, since the [a] would be modified in place.
Something like this:
gs :: ([a] -> [a]) -> [a] -> ([a] -> Bool) -> ???
Does wmap or wfmap exist as part of any language by default, and what is it called?
Answer 1 (thanks to Bergi): Instead of the silly wmap/wfmap signature, we already have until.
Does an in place version of until exist for things like gs?
There is a proverb in engineering which states "Don't generalize before you have at least 3 implementations". There is some truth to it - especially when looking for new functional iteration concepts before doing it by foot a few times.
"Doing it by foot" here means, you should - if there is no friendly helper function you know of - resort to recursion. Write your "special cases" recursively. Preferably in a tail recursive form. Then, if you start to see recurring patterns, you might come up with a way to refactor into some recurring iteration scheme and its "kernel".
Let's for the sake of clarification of the above, assume you never heard of foldl and you want accumulate a result from iteration over a list... Then, you would write something like:
myAvg values =
total / (length values)
where
mySum acc [] = acc
mySum acc (x:xs) = mySum (acc + x) xs
total = mySum 0 values
And after doing this a couple of times, the pattern might show, that the recursions in those where clauses always look darn similar. You might then come up with a name like "fold" or "reduce" for that inner recursion snippet and end up with:
myAvg values = (foldl (+) 0.0 values) / fromIntegral (length values) :: Float
So, if you are looking for helper functions which help with your use-cases, my advice is you first write a few instances as recursive functions and then look for patterns.
So, with all that said, let's get our fingers wet and see how the Jacobi algorithm could translate to Haskell. Just so we have something to talk about. Now - usually I do not use Haskell for anything requiring arrays (containers with O(1) element access), because there are at least 5 array packages I know of and I would have to read for 2 days to decide which one is suitable for my application. TL;DR;). So I stick with lists and NO package dependencies beyond prelude in the code below. But that is - given the size of the example equations we try to solve is tiny - not a bad thing at all. Plus, the code demonstrates, that list comprehensions in lazy Haskell allow for un-imperative and yet performant operations on sets of cells (e.g. in the matrix), without any need for explicit looping.
type Matrix = [[Double]]
-- sorry - my mind went blank while looking for a better name for this...
-- but it is useful nonetheless
idefix nr nc =
[ [(r,c) | c <- [0..nc-1]] | r <- [0..nr-1]]
matElem m (r,c) = (m !! r) !! c
transpose (r,c) = (c,r)
matrixDim m = (length m, length . head $ m)
-- constructs a Matrix by enumerating the indices and querying
-- 'unfolder' for a value.
-- try "unfoldMatrix 3 3 id" and you see how indices relate to
-- cells in the matrix.
unfoldMatrix nr nc unfolder =
fmap (\row -> fmap (\cell -> unfolder cell) row) $ idefix nr nc
-- Not really needed for Jacobi problem but good
-- training to get our fingers wet with unfoldMatrix.
transposeMatrix m =
let (nr,nc) = matrixDim m in
unfoldMatrix nc nr (matElem m . transpose)
addMatrix m1 m2
| (matrixDim m1) == (matrixDim m2) =
let (nr,nc) = matrixDim m1 in
unfoldMatrix nr nc (\idx -> matElem m1 idx + matElem m2 idx)
subMatrix m1 m2
| (matrixDim m1) == (matrixDim m2) =
let (nr,nc) = matrixDim m1 in
unfoldMatrix nr nc (\idx -> matElem m1 idx - matElem m2 idx)
dluMatrix :: Matrix -> (Matrix,Matrix,Matrix)
dluMatrix m
| (fst . matrixDim $ m) == (snd . matrixDim $ m) =
let n = fst . matrixDim $ m in
(unfoldMatrix n n (\(r,c) -> if r == c then matElem m (r,c) else 0.0)
,unfoldMatrix n n (\(r,c) -> if r > c then matElem m (r,c) else 0.0)
,unfoldMatrix n n (\(r,c) -> if c > r then matElem m (r,c) else 0.0)
)
mulMatrix m1 m2
| (snd . matrixDim $ m1) == (fst . matrixDim $ m2) =
let (nr, nc) = ((fst . matrixDim $ m1),(snd . matrixDim $ m2)) in
unfoldMatrix nr nc
(\(ro,co) ->
sum [ matElem m1 (ro,i) * matElem m2 (i,co) | i <- [0..nr-1]]
)
isSquareMatrix m = let (nr,nc) = matrixDim m in nr == nc
jacobi :: Double -> Matrix -> Matrix -> Matrix -> Matrix
jacobi errMax a b x0
| isSquareMatrix a && (snd . matrixDim $ a) == (fst . matrixDim $ b) =
approximate x0
-- We could possibly avoid our hand rolled recursion
-- with the help of 'loop' from Control.Monad.Extra
-- according to hoogle. But it would not look better at all.
-- loop (\x -> let x' = jacobiStep x in if converged x' then Right x' else Left x') x0
where
(nra, nca) = matrixDim a
(d,l,u) = dluMatrix a
dinv = unfoldMatrix nra nca (\(r,c) ->
if r == c
then 1.0 / matElem d (r,c)
else 0.0)
lu = addMatrix l u
converged x =
let delta = (subMatrix (mulMatrix a x) b) in
let (nrd,ncd) = matrixDim delta in
let err = sum (fmap (\idx -> let v = matElem delta idx in v * v)
(concat (idefix nrd ncd))) in
err < errMax
jacobiStep x =
(mulMatrix dinv (subMatrix b (mulMatrix lu x)))
approximate x =
let x' = jacobiStep x in
if converged x' then x' else approximate x'
wikiExample errMax =
let a = [[ 2.0, 1.0],[5.0,7.0]] in
let b = [[11], [13]] in
jacobi errMax a b [[1.0],[1.0]]
Function idefix, despite it's silly name, IMHO is an eye opener for people coming from non-lazy languages. Their first reflex is to get scared: "What - he creates a list with the indices instead of writing loops? What a waste!" But a waste, it is not in lazy languages. What you see in this function (the list comprehension) produces a lazy list. It is not really created. What happens behind the scene is similar in spirit to what LINQ does in C# - IEnumerator<T> juggling.
We use idefix a second time when we want to sum all elements in our delta. There, we do not care about the concrete structure of the matrix. And so we use the standard prelude function concat to flatten the Matrix into a linear list. Lazy as well, of course. That is the beauty.
The next notable difference to the imperative wikipedia pseudo code is, that using matrix notation is much less complicated compared to nested looping and operating on single cells. Fortunately, the wikipedia article shows both. So, instead of a while loop with 2 nested loops, we only need an equivalent of the outermost while loop. Which is covered by our 2 liner recursive function approximate.
Lessons learned:
Lists and list comprehensions can help simplify code otherwise requiring nested loops. (In lazy languages).
Ocaml and Common Lisp have mutability and built in arrays and loops. That makes a package, very convenient when translating algorithms from imperative languages or imperative pseudo code.
Haskell has immutability and no built in arrays and no loops, but instead it has a similarly powerful set of tools, namely Laziness, tail call optimization and a terse syntax. That combination requires more planning (and writing some usually short helper functions) instead of the classical C approach of "Let's write it all in main()."
Sometimes it is easier to write a 2 line long recursive function than to think about how to abstract it.
In FP, you don't usually try to fit everything "inside the loop." You do one step and pass it on to the next function. There are lots of combinations that are useful in different situations. A common replacement for a while loop is a map followed by a takeWhile or a dropWhile, but there are many other possibilities, up to just plain recursion.
I often have to do "induction loading" to prove goals in Coq, where I prove multiple things simultaneously by induction.
The problem is, I often end up with Inductive Hypotheses of the following form:
forall a1 ... an,
Premise1 -> Premise2 -> ... Premisek ->
Conclusion1 /\ Conclusion2 /\ ... Conclusion_m
This is fine, but tactics like eauto really don't know how to handle things like this, so it kills automation most of the time.
What I'm wondering is, is there a way to automatically break such a premise into m different premises, i.e.
forall a1 ... an,
Premise1 -> Premise2 -> ... Premisek ->
Conclusion1
...
forall a1 ... an,
Premise1 -> Premise2 -> ... Premise_k ->
Conclusion_m
The main problem I'm running into is that I don't know how to match with an arbitrary length chain of arrows in LTac. I could hard-code up to a certain length, but I'm hoping there's a better way.
Additionally, if it were possible to do the dual (i.e. split on all combinations of disjunctions in Premise1 .. Premise_k) that would also be useful.
I am not an expert of Ltac, but I gave it a try and came up with the following tactic.
Ltac decomp H :=
try match type of H with
context c [?A /\ ?B] =>
let H' := fresh H in
let Pa := context c[A] in
assert (H' : Pa) by (apply H);
let H'' := fresh H in
let Pb := context c[B] in
assert (H'' : Pb) by (apply H);
clear H;
rename H' into H;
rename H'' into H';
decomp H'
end.
Tactic Notation "decomp_hyp" hyp(H) := decomp H.
decomp H searches occurrences of conjunctions in H, then decomposes it into H' and H'', clean the state and calls itself recursively.
On a trivial example, this seems to work.
Perhaps something like this (minus the debug printouts)?
Ltac foo :=
match goal with
| |- forall q, ?X =>
let x := fresh in intros x; idtac x q ; (try foo); generalize x as q; clear x
| |- ?X -> _ =>
let x := fresh in intros x; idtac x ; (try foo); generalize x; clear x
| |- _ /\ _ => repeat split
end; idtac "done".
Goal forall {T} (a1 a2 a3:T) P1 P2 P3 Q1 Q2 Q3, P1 a1 -> P2 a2 -> P3 a3 -> Q1 /\ Q2 /\ Q3.
foo.
This leaves you with the goals
3 subgoals (ID 253)
============================
forall (T : Type) (a1 a2 a3 : T) (P1 P2 P3 : T -> Type) (Q1 : Prop),
Prop -> Prop -> P1 a1 -> P2 a2 -> P3 a3 -> Q1
subgoal 2 (ID 254) is:
forall (T : Type) (a1 a2 a3 : T) (P1 P2 P3 : T -> Type),
Prop -> forall Q2 : Prop, Prop -> P1 a1 -> P2 a2 -> P3 a3 -> Q2
subgoal 3 (ID 255) is:
forall (T : Type) (a1 a2 a3 : T) (P1 P2 P3 : T -> Type),
Prop -> Prop -> forall Q3 : Prop, P1 a1 -> P2 a2 -> P3 a3 -> Q3
I'm trying to define a unary operation on a set stalk x, whose typical elements are of the form germ x U s. In this case, there is no way to define an operation on general things of the same type as germ x U s in a way that reduces to what I want, so it seems like I really do have to resort to a definition by cases. I attempted the following
definition stalk_mop2 :: "'a ⇒( ('a set × 'a) set ⇒ ('a set × 'a) set ) " where
"stalk_mop2 x y = ( (λ z . if (∃ U s. y= germ x U s ) then
(germ x U ( -⇩a ⇘objectsmap U⇙ s ) ) else undefined) z ) " ,
and got the error message that U s are extra variables on the RHS. It seems like by using this syntax Isabelle does not make the connection between the if hypothesis and the following term, so that although I did bind U and s in the conditional statement, it apparently interprets the next occurrences of U and s (after then) as free variables.
What I really want is just a function that takes x and something of the form germ x U s and returns germ x U ( -⇩a ⇘objectsmap U⇙ s ). Nothing here is recursive.
Is there a way around this problem, or maybe a better way to make definitions by cases that will allow me to define what I want?
Be aware that this is nothing strange about Isabelle's syntax but, there just is no connection between the if-condition and the then- and else-branches. The scope of the existential quantifier naturally ends with then.
If you want to obtain a witness for something you know exists, you can use Hilbert's choice operator, e.g., SOME (U, s). y = germ x U s) gives you a pair (U, s) that satisfies y = germ x U s if such a pair exists (which you made sure by your if-condition), and is undefined otherwise.
So how about:
definition stalk_mop2 :: "'a ⇒(('a set × 'a) set ⇒ ('a set × 'a) set)"
where
"stalk_mop2 x y = ((λz .
if ∃U s. y = germ x U s then
let (U, s) = (SOME (U, s). y = germ x U s) in
germ x U (-⇩a ⇘objectsmap U⇙ s)
else undefined) z)"
Update: You can use multiple lets in one of the following ways
let x1 = e1 in let x2 = e2 in ...
or
let x1 = e1; x2 = e2; ... in ...
I was able to make a nice picture with Elm's share-elm.com any tips for code optimization would be appreciated but I am focusing on the last two lines:
xflip : (number, number) -> (number, number)
xflip pt = ( -1*(fst pt), snd pt)
rot : (number, number) -> (number, number)
rot pt = ( -1*(snd pt), fst pt)
mul : number -> (number, number) -> (number, number)
mul a b = (a*(fst b), a*(snd b))
add : (number, number) -> (number, number) -> (number, number)
add a b = ((fst a)+(fst b), (snd a)+(snd b))
-- implementations of the symmetries of hilbert space curve
t1 : (number, number) -> (number, number)
t1 b = (add (mul 0.5 (-100,-100)) ((mul 0.5) (rot (rot(rot (xflip b))) )))
t2 : (number, number) -> (number, number)
t2 b = (add (mul 0.5 (-100,100)) ((mul 0.5) (b)))
t3 : (number, number) -> (number, number)
t3 b = (add (mul 0.5 (100,100)) ((mul 0.5) ( b)))
t4 : (number, number) -> (number, number)
t4 b = (add (mul 0.5 (100,-100)) ((mul 0.5) (rot (xflip b) )))
--
t : [(number, number)] -> [(number, number)]
t z = (map t1 z) ++ (map t2 z) ++ (map t3 z) ++ (map t4 z)
I don't know if this is the best say to define vector addition or 2D transformations, but I needed to do it somehow. Often done with vector graphics on the graphics themselves, I am working with list of points before they become Path types.
Was this the best way to iterate the rotation function rot ? I needed to rotate 90 degrees left and then right. So I rotated left 3 times:
rot (rot(rot (xflip b)))
Onto the main question, could my last two lines be streamlined:
t : [(number, number)] -> [(number, number)]
t z = (map t1 z) ++ (map t2 z) ++ (map t3 z) ++ (map t4 z)
The list of numbers are will become my Path objects and t1 through t4 are functions. I thought maybe I could iterate over these functions with map. It works in the cases I tried on Github gist: https://gist.github.com/MonsieurCactus/ef285584f1588289b477 Here's what I tried:
t : [(number, number)] -> [(number, number)]
t z = map ( \f -> (map f z)) [t1, t2, t3 ,t4]
The Elm compiler returned the error message:
[1 of 1] Compiling Main ( Main.elm )
Type error on line 49, column 7 to 46:
map (\f -> map f z) [t1,t2,t3,t4]
Expected Type: (Float)
Actual Type: _List
Type error on line 49, column 7 to 46:
map (\f -> map f z) [t1,t2,t3,t4]
Expected Type: Float
Actual Type: (Float, Float)
Maybe I should have tried writing a function [Path] -> [Path] but then I have to get the list of points and change them anyway.
Streamlining the last two lines
Your attempt at shortening the definition of t is in the right direction. But because you map over the list of functions ([t1,t2,t3,t4]), and inside the mapping function you map over the list of points z, you end up with a list of lists of points ([[(number,number)]] instead of [(number, number)]).
So you still need to concat that list of lists. You can also use concatMap instead of a loose concat and map:
t : [(number, number)] -> [(number, number)]
t z = concatMap ( \f -> (map f z)) [t1, t2, t3 ,t4]
Iterating rot
If you don't mind using Float everywhere instead of number, you can change your rot function to take a rotation to perform. Using some basic functions, you could write something like:
rot' : Float -> (Float, Float) -> (Float, Float)
rot' angle point =
let (r,th) = toPolar point
th' = th + angle
in fromPolar (r,th')
rot = rot' (degrees 90)