Wednesday 30 September 2009

functions and funs

To create a function that takes a fun as an argument

   1: mangle(F, X) ->
   2:     F(X).
4> play:mangle(fun(X) -> (X*X) end, 3).

Remember that if you’re defining the fun inline to add the end still – or you’ll see

* 1: syntax error before: ')'

To pass in a function to something that needs a fun, pass a function reference and remember the arity

   1: double(X) ->
   2:     2*X.
6> play:mangle(fun play:double/1, 3).
6

And finally to create a function that returns a fun

   1: getMangler() ->
   2:     (fun(X) ->
   3:         play:double(X) end).
   4:  
1> Mangler = play:getMangler().
#Fun<play.0.65855982>
2> Mangler(3).
6
3> play:mangle(Mangler, 3).
6

and you can naturally just put the getMangler into the call direct:

4> play:mangle(play:getMangler(), 3).
6

Note that this won’t work:

5> play:mangle(fun play:getMangler/0, 3).
** exception error: play:getMangler/0 called with one argument
     in function  play:mangle/2

because you’re attempting to call getMangler with the argument 3.

Monday 28 September 2009

tuples and templates

How can we be sure that we produce and consume tuples that are of the form we expect? Do we have to wait to runtime?

process management

Processes hang around, as you might expect. First of all, pman is the Erlang process manager. Start with

1> pman:start().

Hide system processes to make it a bit clearer for now. If we run the simple message passing example from here, then we can see that a play:receiver() process is left hanging around after everything stops.

2> play:start().
in receiver <0.46.0>
rx: hey <0.47.0>
<0.47.0>
in receiver <0.46.0>

To kill the process, highlight it with a click and choose Trace/Kill. pman has a default 5s refresh, so it’ll not disappear immediately…

favour short tasks

Divide your workload into chunks that are small duration compared to your time-for-delivery.

For long tasks, use mechanisms that allow redundant processing to ensure delivery. Ensure that you start your long tasks first.

message passing

   1: -module(play).
   2: -export([headstrip/1,message/1,start/0,receiver/0,sender/1]).
   3:  
   4: sender(RxPid) ->
   5:     RxPid ! {hey, self()}.
   6:  
   7: receiver() ->
   8:     io:format("in receiver ~p~n",[self()]),     
   9:     receive
  10:         {hey, TxPid} -> io:format("rx: ~p ~p~n",[hey, TxPid])
  11:     end.
  12:  
  13: start() ->
  14:     RxPid = spawn(play, receiver, []),
  15:     spawn(play, sender, [RxPid]).
28> play:start().
in receiver
rx: hey
<0.150.0>

receive blocks, and handles one message

If you want your receiver to hang around, call it again. e.g. add a call to receiver() just before end in the code above. But then – whole process finishes. Is something running in the background still? How do I check? [see http://timbar.blogspot.com/2009/09/process-management.html]

remember to export

Remember that every function called must be exported – not just the bootstrap function you use to kick things off. In this context the error

=ERROR REPORT==== 25-Sep-2009::16:51:33 ===
Error in process <0.98.0> with exit value: {undef,[{play,sender,[<0.97.0>]}]}
indicates that you've forgotten to export the function "sender".

unhandlable messages are just dropped

Messages that receive doesn’t know how to handle are just forgotten. How to do a default response? Try sending eef instead of hey in the code above.

Friday 25 September 2009

difference between foreach and map

map creates a new list from an existing list by applying a function to each existing member.

Use foreach if you want to trigger actions based on list members, not just create new list members.

For example – working from a list of contacts you might want to send a message to everyone whose name matches some string. You’ve no reason to create a new list in this case.

To message an agent – or not – assume we have this function:

   1: -module(play).
   2: -export([message/1]).
   3:  
   4: message({Agent,Location}) when Agent == "John" ->
   5:     io:format("Messaging ~w in ~w~n",[Agent,Location]);
   6: message({Agent,Location}) ->
   7:     io:format("No message for ~w~n",[Agent]).
5> play:message({"John","Buenos Aires"}).
Messaging [74,111,104,110] in [66,117,101,110,111,115,32,65,105,114,101,115]
ok
6> play:message({"Jeff","Buenos Aires"}).
No message for [74,101,102,102]
ok

(Ha, unexpectedly turns strings to lists of ASCII character codes. Need to look into string handling.)

3> lists:foreach(fun play:message/1, [{"John","Buenos Aires"}, {"Jules","Nairobi"}]).
Messaging [74,111,104,110] in [66,117,101,110,111,115,32,65,105,114,101,115]
No message for [74,117,108,101,115]
ok

Surprised by the need to recast our message function explicitly as a fun to pass it to foreach. Need to look into that too. Is it because passing a naked play:message/1 looks like an atom?

function signature dropthrough a bit like case

   1: price_by_age({car,Age}) -> 10 * (10 - Age);
   2: price_by_age({boat,Age}) -> 20 * (20 - Age).

is equivalent to

   1: price_by_age(Vehicle) ->
   2:     case Vehicle of
   3:         {car,Age} -> 10 * (10 - Age);
   4:         {boat,Age} -> 20 * (20 - Age)
   5:     end.

recursive and iterative

   1: sum([H|T]) -> H + sum(T);
   2: sum([]) -> 0.

1 + sum(2,3)
1 + 2 + sum(3)
1 + 2 + 3 + sum()
6
   1: sum(L) -> sum(L,0).
   2:  
   3: sum([H|T],Accumulator) -> sum(T,Accumulator + H);
   4: sum([],Accumulator) -> Accumulator.

sum([2,3], 1)
sum([3], 3)
sum([], 6)
6

Using the accumulator means you can pass state to the next call rather than having to keep all your state hanging around while you progress.

Thursday 24 September 2009

Installing Windows Services programmatically

After a few problems with using the ManagedInstallerClass (passing arguments in), and noticing that the MSDN docs say it’s not meant to be used in any case, I moved to using this sort of code instead:

 

   1: using System;
   2: using System.Configuration.Install;
   3: using System.ServiceProcess;
   4: using System.Threading;
   5: using System.Diagnostics;
   6: using System.IO;
   7: using System.Reflection;
   8: using System.Collections.Generic;
   9:  
  10: namespace SimpleWindowsServiceManager
  11: {
  12:     class Program
  13:     {
  14:         static void Main(string[] args)
  15:         {
  16:             string fullpath = args[0];
  17:             string[] arguments = args[1].Split(' ');
  18:             string servicename = Path.GetFileNameWithoutExtension(Path.GetFileName(args[0]));
  19:             foreach (string arg in arguments)
  20:             {
  21:                 if (arg.Contains("name"))
  22:                 {
  23:                     string[] bits = arg.Split(new char[] { '=' });
  24:                     servicename = bits[1];
  25:                 }
  26:             }
  27:  
  28:             #region Test to see if it's a service
  29:             try
  30:             {
  31:                 AssemblyInstaller.CheckIfInstallable(fullpath);
  32:             }
  33:             catch (Exception ex)
  34:             {
  35:                 Console.WriteLine("Assembly's not installable");
  36:                 return;
  37:             }
  38:             #endregion
  39:  
  40:             #region install
  41:             Assembly assembly = null;
  42:             try
  43:             {
  44:                 assembly = Assembly.LoadFrom(fullpath); // LoadFrom probes path for dependencies -- LoadFile does not.
  45:             }
  46:             catch (Exception ex)
  47:             {
  48:                 Console.WriteLine("Couldn't load assembly: " + ex.Message);
  49:                 return;
  50:             }
  51:             AssemblyInstaller installer = new AssemblyInstaller(assembly, null);
  52:             Dictionary state = new Dictionary();
  53:             try
  54:             {
  55:                 installer.Install(state);
  56:                 installer.Rollback(state);
  57:             }
  58:             catch (Exception ex)
  59:             {
  60:                 Console.WriteLine("Trouble pre-installing assembly");
  61:                 return;
  62:             }
  63:             #endregion
  64:  
  65:             installer = new AssemblyInstaller(assembly, arguments);
  66:             state = new Dictionary();
  67:  
  68:             #region Install service
  69:             try
  70:             {
  71:                 installer.Install(state);
  72:             }
  73:             catch (InvalidOperationException iex)
  74:             {
  75:                 Console.WriteLine("Install not applicable for this assembly:\n" + iex.Message);
  76:                 return;
  77:             }
  78:             catch (Exception ex)
  79:             {
  80:                 Console.WriteLine(ex.Message);
  81:             }
  82:             #endregion
  83:  
  84:             Console.WriteLine("Getting service controller for " + servicename);
  85:             ServiceController controller = new ServiceController(servicename);
  86:  
  87:             #region Start service
  88:             try
  89:             {
  90:                 Console.WriteLine("Starting service");
  91:                 controller.Start();
  92:             }
  93:             catch (Exception ex)
  94:             {
  95:                 Console.WriteLine(ex.Message);
  96:             }
  97:             #endregion
  98:  
  99:             Thread.Sleep(10000);
 100:  
 101:             #region Stop service
 102:             try
 103:             {
 104:                 Console.WriteLine("Stopping service");
 105:                 controller.Stop();
 106:             }
 107:             catch (Exception ex)
 108:             {
 109:                 Console.WriteLine(ex.Message);
 110:             }
 111:             #endregion
 112:  
 113:             Thread.Sleep(10000);
 114:  
 115:             #region Uninstall service
 116:             try
 117:             {
 118:                 installer.Uninstall(state);
 119:             }
 120:             catch (Exception ex)
 121:             {
 122:                 Console.WriteLine(ex.Message);
 123:             }
 124:             #endregion
 125:         }
 126:     }
 127: }

head strip with guard

   1: -module(play).
   2: -export([headstrip/1]).
   3:  
   4: headstrip([]) -> ok;
   5: headstrip([Head|Rest]) when Head > 4 ->
   6:     io:format("~w bingo!~n", [Head]),
   7:     headstrip(Rest);
   8: headstrip([Head|Rest]) -> 
   9:     io:format("~w~n",[Head]),
  10:     headstrip(Rest).
3> play:headstrip([1,2,3,4,5,6,7,8,9]).
1
2
2
3
4
5 bingo!
6 bingo!
7 bingo!
8 bingo!
9 bingo!
ok

Wednesday 23 September 2009

recursively strip head element from list

   1:  -module(play).
   2:  -export([headstrip/1]).
   3:   
   4:  headstrip([]) -> ok;
   5:  headstrip([HeadRest]) -> 
   6:      io:format("~w~n",[Head]),
   7:      headstrip(Rest).