Näytä kirjoitukset

Tässä osiossa voit tarkastella kaikkia tämän jäsenen viestejä. Huomaa, että näet viestit vain niiltä alueilta, joihin sinulla on pääsy.


Viestit - Tommi S.

Sivuja: 1 [2] 3 4 ... 13
21
Edit:  Saatan olla jäljillä, näyttää hieman että ssd tunnistaa vain käännetyn koodin, ei tulkattavaa.  Teinpä kokeeksi ikuisen silmukan joka ajaa "nop" -käskyä:

Koodia: [Valitse]
#!/bin/bash
while :
do
:
done


Joo, tutkin hieman tuon start-stop-daemonin manuaalisivua, ja näyttää siltä että olet todellakin oikeilla jäljillä.

Tein itselleni myös samanlaisen bash-skriptin joka ei tee mitään (paitsi että minun skriptini tekee silmukassa sleep komennon jotta ei turhaan käytetä suoritinta), ja laitoin sen pyörimään. Minun skriptini sai prosessinumeron 2982.

Ssd:n manuaalissa sanotaan tuosta --exec valitsimesta näin:
      -x, --exec executable
              Check for  processes  that  are  instances  of  this  executable
              (according to /proc/pid/exe).

Eli se katsoo tuonne /proc/2982/exe tiedostoon ja katsoo mikä siellä pyörii. Nyt jos ls komennolla katsotaan mitä sieltä löytyy niin nähdään seuraavaa:
Koodia: [Valitse]
tommi@tommi-desktop64:~$ ls -l /proc/2982/exe
lrwxrwxrwx 1 tommi tommi 0 heinä 25 17:11 /proc/2982/exe -> /bin/bash

Eli se onkin /bin/bash joka siellä pyörii, eikä ohjelma nimeltä testi.sh jonka olin luonut.

Mutta — ssd:n manuaalisivua tutkimalla huomaamme että on toinenkin valitsin:
      -n, --name process-name
              Check  for  processes  with  the name process-name (according to
              /proc/pid/stat).

Tämä name valitsin siis katsoo /proc/2982/stat tiedostoa, ja jos katsomme mitä sieltä löytyy niin löydämme seuraavaa:
Koodia: [Valitse]
tommi@tommi-desktop64:~$ cat /proc/2982/stat
2982 (testi.sh) S 2822 2982 2822 34816 3404 4202496 5539 58106 0 0 4 20 2 13 20 0 1 0 128248 13873152 341 18446744073709551615 4194304 5110396 140736121151904 140736121150080 140725522402366 0 65536 4 65538 18446744071579283524 0 0 17 0 0 0 0 0 0
Sieltä siis löytyy tuo testi.sh joka on ohjelmani nimi.

Näiden tutkimusten perusteella vaikuttaisi siis siltä että oikea komento tuolle start-stop-daemonille olisi suunnilleen seuraavanlainen:
Koodia: [Valitse]
sudo start-stop-daemon --start --startas /usr/local/bin/auto_dld --pidfile /var/run/auto_dld.pid --name auto_dld --test
Eli --exec valitsimen sijaan käytetään --startas valitsinta kertomaan mikä komento suoritetaan, ja --name valitsinta kertomaan mikä nimi ohjelmalla on kun se on käynnissä.

22
50 megan tiedostolla tuo ero ei vielä ole mahdottoman suuri vaikka ”seitsemän kertaa hitaampi” onkin. Saatko kokeilluksi saman 500 megan tiedostolla, joka olisi lähempänä todellista testiä?

Lisäsin testitiedoston generoivan skriptin silmukkaan yhden nollan ja 535 megan tiedostolla ajat ovat Perl n. 5 sekuntia ja Python noin 20 sekuntia, eli melko täsmälleen ne samat kertoimet jotka saatiin jo 50 megan tiedostolla (kun oltiin poistettu regexpistä sulkeet, jotka jostain syystä oleellisesti hidastivat Pythonia).

Tuosta 50 megan tapauksestakin voi laskea että jos on vaikka 100kpl 50 megan tiedostoa niin siinä missä Perlillä menee kaikkien noiden läpikäymiseen 0,5s * 100 = 50s, niin Pythonilta menee samaan urakkaan 2,0s * 100 = 3min20s.

23
Tein nyt testin vielä siten että pilkoin tuon tiedoston riveiksi, mutta yllättäen lopputulos ei muuttunut juuri ollenkaan, Python on edelleen n. 7 kertaa hitaampi kuin Perl, eli 3,5s vs. 0,5s.

Muokkaus: Tajusin juuri että tein tuon regexpin hieman hölmösti tuossa Python koodissa, eli tein re.findall(m, line) kun olisin voinut tehdä m.findall(line). Korjasin tuon nyt tuohon koodiin, mutta Python koodi ei nopeutunut kuin ehkä 0,05s, ja tuokin on niin pieni ero että on vaikea sanoa onko se sattumaa vai oikeaa nopeutumista.

Muokkaus2: Huomasin juuri että jos tuosta regexpistä jättää nuo sulkeet pois niin Python versio nopeutuu jonkin verran, ero on enää 2s vs. 0,5s Perlin hyväksi. Tein muutoksen allaoleviin koodipätkiin.

Tässä ohjelma joka generoi tiedoston:
Koodia: [Valitse]
#!/usr/bin/env python3

from random import randint, choice
from string import ascii_uppercase, ascii_lowercase

def sosnum():
  return "%02d%02d%02d-%03d%s" % (
    randint(1,30),
    randint(1,12),
    randint(0,99),
    randint(1,600),
    choice(ascii_uppercase+"0123456789"))

for i in range(1,100000):
  if (i % 10 == 0):
    print()
  for j in range(1,randint(100,1000)):
    print(choice(ascii_lowercase), end="")
  print(sosnum(), end="")

Tässä Python lukija:
Koodia: [Valitse]
#!/usr/bin/env python3

from sys import stdin
import re

m = re.compile('[0-9]{6}-[0-9]{3}[A-Z0-9]')

result = []

for line in stdin:
  result += m.findall(line)

print(len(result))
print(result[0:10])

Ja tässä Perl lukija:
Koodia: [Valitse]
#!/usr/bin/env perl

my @found = ();
my @match;

while(<STDIN>) {
  @match = $_ =~ m/[0-9]{6}-[0-9]{3}[A-Z0-9]/g;
  push(@found, @match);
}

print @found . "\n";
my @first = @found[0..9];
print "['";
print join("', '", @first);
print "']\n";

24
Tein vielä testin jossa ensin kirjoitin yhden julmettoman ison tiedoston jossa on sekaisin kirjaimia ja keksittyjä sosiaaliturvatunnuksia, eli sisältö tällaista: "...othlbdetqnnwimwfzs270995-257Xavckvgim...". Tässä ei siis ole erillisiä rivejä, vaan koko roska on yksi pitkä rivi.

Tässä koodinpätkä joka generoi tuon tiedoston, omasta tiedostostani tuli n. 50 megan kokoinen. Tämä siis laittaa kaiken stdouttiin, josta sen voi tallettaa tiedostoon:
Koodia: [Valitse]
#!/usr/bin/env python3

from random import randint, choice
from string import ascii_uppercase, ascii_lowercase

def sosnum():
  return "%02d%02d%02d-%03d%s" % (
    randint(1,30),
    randint(1,12),
    randint(0,99),
    randint(1,600),
    choice(ascii_uppercase+"0123456789"))

for i in range(1,100000):
  for j in range(1,randint(100,1000)):
    print(choice(ascii_lowercase), end="")
  print(sosnum(), end="")

Sitten Perl ja Python skriptipätkät jotka lukevat stdinistä ja poimivat kaikki löytämänsä sos.turvatunnukset taulukkoon. Molemmat tulostavat lopuksi löytämiensä sos.turvatunnusten määrän ja kymmenen ensimmäistä taulukossa olevaa tunnusta. Omissa testeissäni Perl suoriutui urakasta n. 0,5 sekunnissa kun Pythonilta aikaa meni n. 3,5 sekuntia.
Tässä saattaa tosin vaikuttaa se että Python ei ilmeisesti voi tehdä tuota regexp operaatiota suoraan stdinille, vaan se joutuu ensin lukemaan sen kokonaan muistiin, kun taas Perl lähtee suoraan tekemään tuota hakua. Teen vielä yhden testin jossa tiedosto on pätkitty riveiksi, jos vaikka Python pärjäisi siinä tilanteessa paremmin.

Python:
Koodia: [Valitse]
#!/usr/bin/env python3

from sys import stdin
import re

m = re.compile('([0-9]{6}-[0-9]{3}[A-Z0-9])')
result = re.findall(m, stdin.read())

print(len(result))
print(result[0:10])

Perl:
Koodia: [Valitse]
#!/usr/bin/env perl

my @match = <STDIN> =~ m/([0-9]{6}-[0-9]{3}[A-Z0-9])/g;

print @match . "\n";
my @latest = @match[0..9];
print "['";
print join("', '", @latest);
print "']\n";

25
Noiden regexpien käyttäminen ei välttämättä tuo ilmi koko totuutta. Python skriptin alussa on "import re", mikä siis tarkoittaa että ladataan lisäpalikka joka on itsessään kirjoitettu C:llä, eli tässä todennäköisesti verrataan Perlin ja Pythonin regexp toteutuksia keskenään, jotka molemmat on tod.näk. konepellin alla kirjoitettu C:llä.

Lisäksi noissa regexp toteutuksissa voi ehkä olla erilaisia optimointeja, joten tällainen "korvaa yksi merkki merkkijonolla" saattaa antaa ihan eri tuloksia kuin esim. "etsi oikeanmuotoinen sosiaaliturvatunnus kirjainmoskan seasta".

Tässä omat testiskriptini jotka ottavat numeron ja jakavat sen tekijöihin. Yritin tehdä koodit mahdollisimman identtisiksi molemmissa. Jos joku tietää optimointeja niin niitä saa ehdottaa.
Tässä mitataan siis raakaa laskentaa ja kielen perusominaisuuksia, jakolaskujen nopeutta, yksittäisten käskyjen suoritusnopeutta, silmukoiden nopeutta, jne.

Omalla koneellani Python3 versio vie n. 11 sekuntia ja Perl versio n. 5 sekuntia.

Koodia: [Valitse]
#!/usr/bin/env perl

my $n = 1032445450;
my @factors = ();
my $i = 1;

while($i < $n) {
  if ($n % $i == 0) {
    $n /= $i;
    push(@factors, $i);
  }
  $i++;
}

push(@factors, $n);
@factors = sort { $a <=> $b } @factors;

# identtinen tulostus Python version kanssa
my $last = pop(@factors);
print "[";
foreach(@factors) { print "$_, "; }
print "$last]\n";

Koodia: [Valitse]
#!/usr/bin/env python3

n = 1032445450
factors=[]
i=1

while(i < n):
  if (n % i == 0):
    n //= i
    factors.append(i)
  i += 1

factors.append(n)
factors.sort()

print(factors)

26
Tuo streamripper ja myös Stackoverflow:sta löytyvä Python skripti näyttäisivät toteuttavan tuon nauhoituspuolen, eli ne osaavat nauhoittaa ja pilkkoa striimin erillisiin pieniin tiedostoihin. Ainoa mikä sitten jää puuttumaan on jonkinlainen soitin joka osaisi soittaa nuo tiedostot, ja jonka soittojonoon voisi sitten lisätä uusia tiedostoja lennosta.

Streamripper näköjään näyttäisi vaativan että striimin metatiedoissa mainitaan milloin kappale vaihtuu, se ei muuten osaa pilkkoa striimiä eri tiedostoihin, eli jos live-lähetyksen metatiedoissa ei ole mitään kappaleenvaihtoja niin tämä ei välttämättä toimi. Tuo Python skripti sen sijaan näyttäisi pilkkovan striimin tasakokoisiin tiedostoihin, eli siinä ei ole tällaista ongelmaa.

27
Ongelmaksi tosiaan muodostuu se että tiedoston alkupäästä ei voi poistaa tavaraa, eli se kasvaa loputtomasti jos live-lähetys jatkuu loputtomasti.

Jos olisi sellainen ohjelma joka osaisi nauhoittaa tuota striimiä useampaan tiedostoon, siten että kun yksi tiedosto kasvaa vaikka 50 megan kokoiseksi niin se jatkaa nauhoittamista uuteen tiedostoon, niin sittenhän niitä vanhempia tiedostoja voisi aina poistella. Sitten vain tarvittaisiin joku skripti joka lisää uuden tiedoston soitto-ohjelman soittojonoon aina kun tuo nauhoitusohjelma aloittaa uuden tiedoston nauhoittamisen. Toinen skripti voisi sitten poistella noita vanhempia tiedostoja levyltä.

Tulee mieleen muutama ongelmakohta, mutta jos nämä ratkaisee niin pitäisi toimia:
- löytyykö tuollaista ohjelmaa joka osaa pätkiä nauhoituksen eri tiedostoihin, ja vielä siten että varsinaiseen ohjelmavirtaan ei tule mitään häiritseviä pätkimisiä?
- löytyykö soitto-ohjelma jonka soittojonoon voi lennosta lisätä tiedostoja?
- pystyykö soitto-ohjelma soittamaan eri tiedostoja saumattomasti yhteen, ettei tule edellä mainittuja pätkimisiä ohjelmavirtaan?

Tietenkin jos löytyy joku ohjelma johon voi säätää 2 tunnin puskuroinnin, niin se saattaisi toimia ihan sellaisenaan.

28
Nimettyyn putkeen taitaa mahtua kerralla vain 4kilotavua tai 64kilotavua, joten sinne ei kovin montaa sekuntia audiota tallenneta odottamaan uudelleen lähetystä.

Olisiko mahdollista tallentaa alkuperäistä live-lähetystä palvelimen kovalevylle vaikka .wav tiedostoon, ja sitten kun tunti on kulunut niin alkaa toistamaan tätä tiedostoa toiseen striimiin, siis samalla kun se live-lähetys edelleen nauhoittaa tiedoston loppuun lisää tavaraa?

Tiedostoa kyllä pitäisi pystyä lukemaan alusta samalla kun toinen prosessi lisää loppuun koko ajan uutta tavaraa, mutta esim. se kannattaa selvittää että mahtuuko koko lähetys palvelimen kovalevylle, ym.

29
Osaisiko joku kertoa, mitä ohjelman lopettaminen päätteestä oikein tarkoittaa.

Voin käynnistää Writerin taustalle päätteestä komennolla

lowriter -accept="socket,host=localhost,port=2002;urp;" &

Writerin ikkuna ilmestyy näytölle, ja kaikki näyttää toimivan normaalisti. Mutta kun lopetan Writerin päätteestä (kill), Writer-ikkuna toimii yhä. Jos käynnistän Writerin edustatyöksi, käynnistävä pääte lukkiutuu siksi ajaksi, kun Writer on käynnissä, mikä sekin on ihan normaalia. Jos sen sijaan keskeytän Writer-työn päätteeltä (ctrl-Z), Writer-ikkuna jumittuu.

Voisinko jollain tavalla lopettaa Writerin päätteestä, vaikka se ei olisikaan turvallista, koska tekstimuutokset voisivat sillion hävitä.

Kyllä sen pitäisi sammua kill komennolla, jos vain osaa antaa oikean id-numeron. Kun ohjelman käynnistää päätteestä taustalle, niin ne numerot jotka komentorivi näyttää (tyyliä [1] 1234) ovat job-id ja prosessiryhmä-id.

Jos haluaa lopettaa ohjelman job-id:n perusteella niin numero täytyy antaa prosenttimerkin kanssa, eli:
Koodia: [Valitse]
kill %1
Jos haluaa lopettaa ohjelman prosessiryhmä-id:n perusteella niin numero täytyy antaa negatiivisena, ja jotta kill ei luule miinusmerkkiä jonkin kytkimen alkumerkiksi, pitää numero erottaa kytkimistä kahdella viivalla, eli:
Koodia: [Valitse]
kill -- -1234
Jos haluaa lopettaa ohjelman prosessi-id:n perusteella, täytyy kyseinen numero kaivaa ensin ps komennolla esiin, jonka jälkeen sen voi antaa sellaisenaan kill komennolle.

Jos ohjelma jostain syystä käynnistää useita eri prosesseja, niin kaikki prosessit voi sammuttaa joko yksitellen prosessi-id:n perusteella, tai sammuttamalla prosessiryhmän, tai sammuttamalla session johtajan. Nämä id-numerot voi kaivaa esille esim. komennolla ps -j. PID on prosessi-id, PGID on ryhmä-id, ja SID on sessiojohtajan-id.

Oletuksena kill lähettää ohjelmalle signaalin numero 15 (symboliselta nimeltään TERM tai SIGTERM). Ohjelmat voivat jossain määrin itse määritellä miten ne reagoivat eri signaaleihin, joten ohjelma ei välttämättä heti sammu signaalin saatuaan. On kuitenkin olemassa signaali numero 9 (KILL tai SIGKILL) jota ohjelma ei voi ohittaa vaan se sammutetaan väkisin.

Jos siis pelkkä kill ei jostain syystä toimi, niin ohjelman voi sammuttaa väkisin komennolla kill -9 1234 tai kill -KILL 1234.


Jos haluan ajaa ohjelmaa, joka muuttaa Writerin tekstiä, miten voin laittaa sen skriptiin niin, että se aloittaa tekstin muuttelun vasta sitten, kun Writer on käynnistynyt kunnolla. Ajastaminen ei ehkä ole hyvä ajatus, koska Writerin käynnistysaika  riippuu monista tekijöistä.

Teele

Kaikkien muiden ohjelmien näkökulmasta Writer on käynnistynyt sitten kun se on käynnistynyt. Ne eivät tunne mitään "käynnistynyt kunnolla" käsitettä, vaan käynnissä oleva Writer on käynnissä, se vaan tekee käynnissä ollessaan niin että se ensin valmistelee käyttöliittymää ym., jolloin ihmiskäyttäjän mielestä ohjelma ei ole "kunnolla" käynnistynyt. Tähän ei taida olla mitään muuta ratkaisua kuin se että Writerin pitää pystyä itse jotenkin viestittämään milloin se on "kunnolla käynnistynyt", eli jos Writerista löytyy jokin ominaisuus jolla kyseistä käynnistymisen astetta voisi kysellä niin silloin tämä voisi olla mahdollista. En tosin tiedä onko kyseistä ominaisuutta olemassa.

30
Tässä on pari hyvää artikkelia joissa kerrotaan noista dekoraattoreista (suomennos on kai "koristelija").

http://www.artima.com/weblogs/viewpost.jsp?thread=240808
http://www.artima.com/weblogs/viewpost.jsp?thread=240845

Lyhyesti sanottuna kyse on siitä että voit luoda funktioita jotka luovat uusia funktioita, ja näiden avulla sitten muokata haluamiesi funktioiden toimintaa, ja tämä @-merkki on siisti merkintätapa kun näitä funktioita luovia funktioita haluaa käyttää. Koko ajatus voi olla aluksi hieman vieras jos ei ole esim. tutustunut funktionaaliseen ohjelmointiin, eikä näiden koristelijoiden käyttö ole useimmissa tapauksissa mitenkään välttämätöntä, ilmankin pärjää pitkälle.

31
Noihin monadeihin on nyt hieman opiskelu tökännyt. Katsoin noita luentoja, mutta ei oikein täysin auennut monadien idea. Lisäksi olen yhtä tutoriaalia ja sen jatko-osaa vähän lukenut, mutta ei siltikään oikein sytytä. Täytyy ehkä lukea vielä jotain muuta. Muistaakseni learnyouahaskell kirjassa oli myös jotain monadeista. Real world Haskell kirjassa on ainakin, mutta en ole sitä vielä ehtinyt lukemaan.

Haskell.orgissa on tällainen lista monadi tutoriaaleista:
http://www.haskell.org/haskellwiki/Monad_tutorials_timeline
Listan ensimmäinen tutoriaali näyttää olevan kurssin opettajan Phil Wadlerin kirjoittama, ja tuota näemmä kehutaan.

32
Tyyppejä määritellessä käytetään kahta avainsanaa, type ja data.
Type toimii kuten esim. C-kielen typedef, eli sillä voi antaa synonyymin jollekin tyypille, jotta koodi olisi helppolukuisempaa.
Data avainsanalla taas määritellään varsinaiset tietotyypit.
Tietotyypin määrittelyssä annetaan tietotyypin =-merkin vasemmalla puolella tietotyypin nimi, ja oikealla puolella tietotyypin konstruktorimääritelmiä. Tietotyypin nimeä käytetään tyyppimäärittelyissä, eli esim. kun ilmoitetaan minkä tyyppisiä parametreja funktio hyväksyy, ja konstruktoreita puolestaan käytetään ns. "oikean koodin" puolella.

Esimerkki:
Koodia: [Valitse]
type Nimi   = String
type Ikä    = Int
type Pituus = Double

data HenkilöTyyppi = Henkilö Nimi Ikä Pituus

henkilönNimi :: HenkilöTyyppi -> Nimi
henkilönNimi (Henkilö nimi ikä pituus) = nimi

henkilönNimi :: HenkilöTyyppi -> Ikä
henkilönNimi (Henkilö nimi ikä pituus) = ikä

henkilönNimi :: HenkilöTyyppi -> Pituus
henkilönNimi (Henkilö nimi ikä pituus) = pituus

Ylläolevassa koodissa HenkilöTyyppi on tyyppinimi, ja sitä käytetään tyyppimäärittelyissä. Henkilö on puolestaan konstruktori jolla voidaan luoda Henkilö-tyyppiä oleva "olio" tai arvo. Kuulemma usein Haskell koodissa tyyppinimi ja konstruktori ovat samannimisiä, eli ylläoleva HenkilöTyyppi määritelmä voisi olla tämänlainen:
Koodia: [Valitse]
data Henkilö = Henkilö Nimi Ikä PituusTässä kannattaa pitää mielessä että vaikka tässä Henkilö-tyyppi ja Henkilö-konstruktori ovatkin samannimisiä niin ne ovat aivan eri asioita.

Interaktiivisessa tulkissa voidaan luoda henkilö näin:
Koodia: [Valitse]
let h = Henkilö "Matti" 38 183.4Kyseinen Henkilö poikkeaa esim. Pythonin olioista siten että ei ole mitään kätevää keinoa tutkia henkilön "attribuutteja", vaan täytyy luoda funktio joka ottaa parametrina Henkilön ja palauttaa jonkun henkilön attribuutin. Koko ajattelutapa on funktionaalisessa ohjelmoinnissa täysin erilainen kuin proseduraalisessa olio-ohjelmoinnissa.

Funktion argumenttien sovituksessa käytetään nimenomaan konstruktoreita, mutta ikäänkuin käänteisesti, eli jos argumentti voitaisiin luoda kyseisellä konstruktorilla niin silloin sovitus tapahtuu, ja argumentin arvot sijoitetaan sovituksessa annettuihin muuttujiin.

Haskellissa on valmiina tyyppi nimeltä Maybe, jolla on kaksi konstruktoria, Just sekä Nothing.
Haskellissa lista voi sisältää vain yhden tyyppisiä alkioita, mutta jos esim. listalla säilytetään vaikka lämpötiloja eri vuorokauden aikoina, niin miten esitetään lämpötila sellaisena aikana jolloin lämpömittari on ollut rikki? Tällaiselle paikalle ei voi laittaa esim. tekstiä "ei saatavilla", sillä numeroita sisältävä lista ei voi sisältää tekstiä (esim. Pythonissa voitaisiin listalle laittaa puuttuviin paikkoihin arvo 'None'). Toinen vaihtoehto olisi laittaa kys. paikalle todella korkea tai todella matala lämpötila, esim. -99999999 tai +999999999, mutta tämä tekee lämpötilojen käsittelystä monimutkaista.
Ratkaisu tähän on Maybe-tyyppi. Listan voidaan määritellä olevan tyyppiä [Maybe Int], jolloin lista voi sisältää joko alkioita jotka sisältävät numeroita tai myös alkioita joiden arvona on Nothing. Esim:
Koodia: [Valitse]
>>> let lista = [Just 1, Just 2, Nothing]
>>> :type lista
  lista :: [Maybe Integer]
Nyt lämpötiloja käsittelevät funktiot voidaa määritellä niin että Nothing-konstruktorilla luodun arvon kohdatessaan ne käsittelevät sen oikein, esim. päivän keskilämpötilaa laskettaessa Nothing-arvot hypätään kokonaan yli. Näin olemme saaneet käyttöön Pythonin 'None'-arvoa muistuttavan arvon Nothing, jota voidaan käyttää minkä tahansa tietotyypin kanssa.

33
Haskell ohjelmissa funktioiden yhteydessä näkee funktioiden tyyppimäärittelyjä, jotka näyttävät tältä:
Koodia: [Valitse]
funktio :: a -> b -> c
Nämä tyyppimäärittelyt eivät ole varsinaista funktion koodia, vaan niillä vain määritellään minkä tyyppisiä parametreja funktio käsittelee ja minkä tyyppisiä arvoja funktio palauttaa. Haskellissa tyyppimäärittelyt eivät ole useimmiten (koskaan?) pakollisia, vaan koodi toimii ilmankin, kun taas Javassa tai C:ssä kääntäjä ei pysty kääntämään ohjelmaa ellei se tiedä minkä tyyppisiä parametreja funktiot käsittelevät.

Tässä muutama funktio ilman tyyppimäärittelyjä:
Koodia: [Valitse]
import Char

tuplaa a = 2 * a

sulkuihin t = "(" ++ t ++ ")"

tuplaaLista []    = []
tuplaaLista (a:b) = (2 * a):(tuplaaLista b)

isoiksiKirjaimiksi []          = []
isoiksiKirjaimiksi (pää:häntä) = (toUpper pää):(isoiksiKirjaimiksi häntä)

Tässä samat tyyppimäärittelyjen kanssa:
Koodia: [Valitse]
import Char

tuplaa :: Int -> Int
tuplaa a = 2 * a

sulkuihin :: String -> String
sulkuihin t = "(" ++ t ++ ")"

tuplaaLista :: [Int] -> [Int]
tuplaaLista []    = []
tuplaaLista (a:b) = (2 * a):(tuplaaLista b)

isoiksiKirjaimiksi :: String -> String
isoiksiKirjaimiksi []          = []
isoiksiKirjaimiksi (pää:häntä) = (toUpper pää):(isoiksiKirjaimiksi häntä)

Molempia versioita voi testata ja ne toimivat aivan samalla tavalla.

Vaikka tyyppejä ei tarvitse erikseen funktioille määritellä, niin Haskellin tyypit ovat kuitenkin erittäin "vahvoja" ja "tiukkoja", mikä tarkoittaa sitä että kääntäjä pystyy kääntämisen aikana päättelemään käytetäänkö jossain funktiossa väärän tyyppisiä parametreja. Tästä johtuen Haskell ohjelmissa ei voi koskaan esiintyä mm. Javasta tuttuja "null pointer exception" tyyppisiä virheitä, sillä Haskell kääntäjä antaa varoituksen jos edes tuollaisen virheen mahdollisuus on olemassa.

Vaikka funktion tyyppejä ei ole pakko määritellä, niin tyyppimäärittelyjen kirjoittaminen kuitenkin helpottaa ymmärtämään mitä minkäkin funktion on tarkoitus tehdä, joten on suositeltua että esim. uuden funktion laatiminen aloitetaan siitä että kirjoitetaan funktion tyyppimäärittely, koska tämän jälkeen on paljon selkeämpää hahmottaa mitä funktion on tarkoitus tehdä.

34
Poimintoja luennosta 7:

Haskell on laiska kieli, mikä tarkoittaa että laskutoimituksia ei suoriteta ennen kuin niiden tuloksia tarvitaan. Tämä mahdollistaa esim. äärettömien listojen käsittelyn, koska ääretöntä listaa ei luoda muistiin kerralla, vaan listan seuraava alkio lasketaan vasta kun rekursiivinen funktio tai muu sitä pyytää.

Tässä esimerkki hitaalla fibonaccin lukuja laskevalla funktiolla:
Koodia: [Valitse]
-- tallennetaan tämä tiedostoon fib.hs
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)


-- käynnistetään ghci ja ladataan yllä oleva fib funktio
:load fib.hs

-- lasketaan 30. fibonaccin luku
fib 30
-- laskeminen kestää kauan... 832040

-- tallennetaan 30. fibonaccin luku muuttujaan
let a = fib 30
-- toimenpide on heti valmis, sillä 30. fibonaccin lukua ei vielä oikeasti laskettu
-- Haskell laskee sen vasta kun a:n arvoa kysytään:
a
-- laskeminen kestää kauan... 832040

-- a:n arvoa ei kuitenkaan lasketa joka kerralla uudestaan, vaan kun se on kerran laskettu niin palautetaan valmiiksi laskettu arvo
a
-- toimenpide on heti valmis - 832040

35
Tässä helmi luennosta 6:

Laskuoperaation suoritusjärjestys vaikuttaa siihen miten kyseisen laskuoperaation voi suorittaa rinnakkain esim. useammalla prosessorilla.

Esimerkiksi peräkkäiset jakolaskut täytyy suorittaa tietyssä järjestyksessä jotta saataisiin oikea tulos, mutta kertolaskut voi suorittaa missä järjestyksessä tahansa. Esimerkki:
Koodia: [Valitse]
let a = 500.0; b = 2.0; c = 7.0; d = 8.0; e = 4.0; f = 3.5

a / b / c / d / e / f 
-- tulos: 0.31887755102040816

-- Jos yritetään laskea eri järjestyksessä, tulos ei ole enää sama.
-- Yritetän laskea alkuosa ja loppuosa erikseen, ja sitten jakaa lopuksi ensimmäinen tulos jälkimmäisellä:
(a / b / c) / (d / e / f)
-- tulos: 62.50000000000001

-- Jakolasku voidaan muuntaa kertolaskuksi muuntamalla jakajat käänteisluvuiksi.
let _b = 1/b; _c = 1/c; _d = 1/d; _e = 1/e; _f = 1/f

a * _b * _c * _d * _e * _f
-- tulos: 0.31887755102040816

-- Kertolaskun voi laskea missä järjestyksessä tahansa, ja saadaan silti aina sama tulos.
(a * _b * _c) * (_d * _e * _f)
-- tulos: 0.31887755102040816

a/b/c/d/e/f == (a*_b*_c) * (_d*_e*_f)
a/b/c/d/e/f == (a*_b) * (_c*_d) * (_e*_f)

a/b/c/d/e/f /= (a/b/c) / (d/e/f)
a/b/c/d/e/f /= (a/b) / (c/d) / (e/f)

Jakolaskun a/b/c/d tapauksessa, ennen kuin voidaan jakaa d:llä täytyy odottaa että laskun a/b/c tulos on valmistunut, mutta käänteislukujen kertolaskussa a*_b*_c*_d ei tarvitse odottaa muiden tulosten valmistumista, joten a*_b voidaan lähettää laskettavaksi prosessorille 1, ja _c*_d voidaan lähettää prosessorille 2, ja lopuksi prosessorien palauttamat tulokset voidaan kertoa keskenään.

36
Tuossa aiemmin mainitsemassani htdp kirjassa neuvotaan kätevä sääntö rekursiivisten tietotyyppien käsittelyyn. Sääntö on sellainen että jokaista rekursiivisesti määriteltyä tietotyyppiä voi käsitellä funktiolla jonka määritelmä on samaa muotoa kuin tietotyypin määritelmä.

Esim. kun lista määritellään rekursiivisesti näin:
Koodia: [Valitse]
lista = []        (tyhjä lista)
lista = pää:häntä (jossa häntä on lista)

niin listan käsittelyyn käy funktio jonka määritelmä näyttää melkein samalta kuin listan määritelmä:
Koodia: [Valitse]
käsitteleLista []        = jotain
käsitteleLista pää:häntä = käsittele pää
                           ja käsitteleLista häntä

eli kun listan määritelmä hännän kohdalla palaa rekursiivisesti takaisin itseensä, niin samoin funktio joka käsittelee listaa palaa hännän kohdalla rekursiivisesti takaisin itseensä, eli jos tietää miltä tietotyypin määritelmä näyttää niin siitä voi ottaa suoraan mallia kun laatii funktiota joka käsittelee kys. tietotyyppiä. Tämä sama pätee myös muihin rekursiivisiin tietotyyppeihin kuin vain listoihin.

Tässä muutama harjoitelmafunktio jotka käsittelevät listoja. 
Koodia: [Valitse]
import Char

-- laskee yhteen listan numeroita
laskeYhteen :: [Int] -> Int
laskeYhteen [] = 0
laskeYhteen (pää:häntä) = pää + laskeYhteen häntä

-- etsii suurimman numeron listalta
-- suurin [] tapausta ei ole määritelty koska suurin alkio tyhjästä ei ole mielekäs toimenpide
suurin :: [Int] -> Int
suurin (pää:[])    = pää
suurin (pää:häntä) = if pää > suurin häntä
                     then pää
                     else suurin häntä
                     
-- laskee montako kertaa x esiintyy listalla
laskeEsiintymät :: (Eq a) => a -> [a] -> Int
laskeEsiintymät x []          = 0
laskeEsiintymät x (pää:häntä) = if pää == x
                                then 1 + laskeEsiintymät x häntä
                                else     laskeEsiintymät x häntä

-- muuntaa merkkijonon isoiksi kirjaimiksi
isoiksiKirjaimiksi :: String -> String
isoiksiKirjaimiksi []          = []
isoiksiKirjaimiksi (pää:häntä) = (toUpper pää):(isoiksiKirjaimiksi häntä)

37
Cabalin ja varmaan myös APT:in innoituksen lähteenä on luultavasti toiminut edellisessä viestissäkin mainittu Perl ohjelmointikieleen liittyvä CPAN-järjestelmä, joka ilmeisesti tuli käyttöön joskus vuoden 1990 paikkeilla. CPAN on keskitetty varasto erilaisia Perl lisukkeita, jotka siis löytyvät kaikki yhdestä paikasta, yhden nettiosoitteen takaa, ja jotka voi asentaa helposti yhdellä komennolla.

Apt-get ja Ruby ohjelmointikielen yhteydessä toimiva RubyGems ovat siinä mielessä samantyyppisiä että kun kirjoittaa terminaaliin apt-get install PAKETTI tai gem install PAKETTI niin molemmat järjestelmät lataavat paketin ja sen vaatimat muut paketit tietystä nettiosoitteesta ja asentavat ne.

Cabal toimii Haskell paketeille samalla tavalla, eli kun kirjoittaa terminaaliin cabal install PAKETTI, niin paketti ja sen vaatimat muut paketit ladataan ja asennetaan. Hackage on sitten se keskitetty tietokanta josta paketit ladataan, ja löytyy osoitteesta hackage.haskell.org.

38
Kun yritin ladata tuota harjoitustiedostoa labweekexercise.hs ghci:ssa niin tuli virheilmoitus että moduulia Test.Quickcheck ei löydy. (Could not find module `Test.QuickCheck')

Haskellille on olemassa moduulien latain nimeltä cabal joka on hieman samanlainen kuin Rubyn Rubygems, tai Debianin apt-get, eli komentorivillä annetaan käsky mikä moduuli halutaan, ja cabal lataa, kääntää ja asentaa kyseisen moduulin.

Sain harjoitustiedoston ladattua Haskell tulkkiin näiden komentojen jälkeen:
Koodia: [Valitse]
sudo apt-get install cabal-install
cabal update
cabal install Quickcheck

cabal update käskyllä haetaan uusimmat pakettilistaukset palvelimelta, ja cabal install komennolla asennetaan haluttu moduuli.

39
Yleistä keskustelua / Vs: Microsoft/Skype
« : 15.10.11 - klo:15.35 »
Joku tyyppi, taisi olla joku ex-Nokialainen, kirjoitti joku aika sitten blogissaan tästä Microsoftin Skype ostoksen vaikutuksesta Nokiaan.

Kuvio menee niin että päästäkseen kunnolla markkinoille esim. USA:ssa täytyy puhelinvalmistajan tehdä yhteistyötä puhelinliittymiä kauppaavan puhelinyhtiön kanssa. Tässä Nokia on aiemmin pahasti epäonnistunut.
Toinen muuttuja yhtälössä on se että puhelinyhtiöt eivät suostu koskemaan edes pitkällä tikulla mihinkään mikä on yhteistyössä Skypen kanssa, sillä Skype uhkaa tuhota koko puhelinyhtiöiden perinteisen bisneksen. Nokia oli vahvasti panostamassa Windows pohjaisiin älypuhelimiin, mutta välittömästi sen jälkeen kun tuli uutinen että Microsoft ostaa Skypen, Stephen Elop vaihtoi Nokian tärkeimmäksi painopisteeksi halvemman luokan peruspuhelimet.

40
Olipa hauska yhteensattuma löytää tällainen ketju täältä, sillä aloin itse juuri pari päivää sitten lukemaan Real World Haskell kirjaa, jota voi lukea osoitteesta: http://book.realworldhaskell.org/read/

Olen joskus aiemmin lukenut hieman Learn You a Haskell for Great Good! kirjaa, joka on eräänlainen kuvitettu ja sarjakuvamainen Haskell oppikirja. (voi lukea osoitteessa: http://learnyouahaskell.com/ )

Nyt kun olen noita molempia kirjoja jonkin verran lukenut, niin mielipiteeni on että tuo Real World Haskell on jotenkin selkeämpi ja helpommin ymmärrettävä, vaikka se onkin paljon asiallisempi eikä yritä olla yhtä viihdyttävä kuin tuo Learn You a Haskell for Great Good!.

Näköjään tuolla Learn You a Haskell kirjan FAQ:ssa on linkki tällaiselle sivulle jossa voi kokeilla Haskellia interaktiivisessa päätteessä suoraan selaimesta: http://tryhaskell.org/

Rekursion ja rekursiivisten tietotyyppien hahmottamisessa itseäni ainakin on auttanut huomattavasti How To Design Programs kirja. (voi lukea osoitteessa: http://htdp.org/ )
Kirjan käyttämä ohjelmointikieli ei tosin ole Haskell, vaan Lisp perheeseen kuuluva Scheme, mutta itse asia eli rekursio ja rekursiiviset tietotyypit toimivat samalla periaatteella kaikilla kielillä, ja tuossa kirjassa ne selitetään mielestäni erittäin selkeästi ja aloittelijaystävällisesti.
Lisäksi kirjaan kuuluu DrScheme niminen ohjelmointiympäristö jossa on oma moodinsa juuri tuolle kirjalle, jonka avulla mm. ohjelmointiympäristön uusia ominaisuuksia kytketään päälle sitä mukaa kun kirja etenee. Rajoittamalla ohjelmointiympäristön ominaisuuksia voidaan esim. antaa alkuvaiheessa aloittelijalle hyvin selkeitä virheilmoituksia.

Sivuja: 1 [2] 3 4 ... 13