Although Keter is flexible and general enough to be used with various kind of applications and web frameworks, here I’m going to assume you’re using it to deploy Yesod applications. Moreover, I’ll assume you’re using Yesod’s scaffolding, as it is the preferred way to write production ready applications.
I’m also taking for granted that you’ve already installed on your server system whatever DBMS that your Yesod app needs, and have also created the app’s databases.
It is always advisable to compile on the development machine rather than the
production server, to avoid utilising its resources for building (specially
considering that GHC can make use of a fair amount of them). So, assuming the
architectures match, you can just install keter
on you local machine:
$ stack install keter
And then put the binary on the server (example.com):
$ scp ~/.local/bin/keter root@example.com:/root/
It’s a good practice to have a dedicated keter user, so you don’t have to deploy as root each time:
# useradd keter
# passwd keter
The directory tree needed on the server is as follows:
keter
├── bin
│ └── keter
├── etc
│ └── keter-config.yaml
├── incoming
└── app.keter
So create it, copy the binary to /opt/keter/bin
, and make sure
/opt/keter/incoming
it’s owned by the keter user (we’ll take care of the
keter-config.yaml
configuration later):
# mkdir -p /opt/keter /opt/keter/bin /opt/keter/etc /opt/keter/incoming
# cp /root/keter /opt/keter/bin
# touch /opt/keter/keter-config.yaml
# chown -R keter:keter /opt/keter/icoming
While you could just execute /opt/keter/bin/keter
directly, it’s better to
register it as a job in your Init System.
Create a file /etc/systemd/system/keter.service
, with the contents:
[Unit]
Description=Keter
After=network.service
[Service]
Type=simple
ExecStart=/opt/keter/bin/keter /opt/keter/etc/keter-config.yaml
[Install]
WantedBy=multi-user.target
Enable the service:
$ sudo systemctl enable keter
Now you can start keter with (don’t do it just yet, as we still need to write the keter configuration file):
$ sudo systemctl start keter
Create a file /etc/init/keter.con
, with the contents:
start on (net-device-up and local-filesystems and runlevel [2345])
stop on runlevel [016]
respawn
console output
exec /opt/keter/bin/keter /opt/keter/etc/keter-config.yaml
Now you can start keter with (don’t do it just yet, as we still need to write the keter configuration file):
$ sudo start keter
The Keter configuration at /opt/keter/etc/keter-config.yaml
is pretty
straight forward:
root: ..
listeners:
# HTTP
- host: "*4" # Listen on all IPv4 hosts
port: 80
# HTTPS
#- host: "*4"
#port: 443
#key: key.pem
#certificate: certificate.pem
# env:
# key: value
The root
option points, as expected, to /opt/keter
.
Make sure to change the port
option if you’re reverse forwarding from a
fronted server like Nginx or Apache (more on this later).
If you’re serving your application over SSL (and you should), uncomment the
HTTPS section, then point the key
option to your privkey.pem
file, and the
certificate
option to your fullchain.pem
file.
The env
option, keeps pairs of keys and values. The main set of values
you’ll need here are your Database credentials. You’ve probably already
configured database credentials in the database
section in the
config/settings.yaml
file, so you’ll notice you need some environment
variables like MYSQL_USER
, MYSQL_PASSWORD
, etc. If you’re using
MySQL/MariaDB; Or PGUSER
, PGPASS
, etc. If you’re using PostgreSQL. You
get the idea.
This is how it will look like for a PostgreSQL Database where only the user and password are different between the development and production servers (be sure to keep the quotes).
env:
PGUSER: "user"
PGPASS: "password"
The Keter configuration file for your Yesod application lives in
config/keter.yml
. Set user-edited
to true
, so you’re able to execute
yesod keter
later on.
Locate the copy-to
option and configure it to use the keter
user and your
server domain (or IP address):
copy-to: keter@example.com:/opt/keter/incoming/
This will allow you to deploy your application with:
$ stack -- exec yesod keter
The most important part of the Keter configuration is perhaps the hosts
option of the webapp
stanza, the hosts you declare here are the ones that your
application is going to respond to. Unless you’re using a separate domain for
serving static files, be sure to keep the hosts
option of the static-files
stanza in sync with the webapp
one.
This one here is a pretty common error message when trying to deploy a Yesod application (and failing miserably):
There is more than one reason for this, but the main one is that the domain name
or IP address doesn’t exactly match one of the hosts provided in the hosts
option.
If you’re serving only one application and using Keter as the main server
listening on port 80
, then having your domain name in hosts
will pretty much
suffice, BUT most of the time, even if your serving only one application, you’re
probably using a frontend server like Nginx or Apache, in which case you
have to consider the port the reverse proxy is pointing to.
Take for instance this Nginx reverse proxy configuration for an app that lives
on blog.example.com
server {
listen 80;
server_name blog.example.com;
location / {
proxy_pass http://127.0.0.1:4321;
}
With a Keter configuration that has:
listeners:
- host: "*4" # Listen on all IPv4 hosts
port: 4321
Then you have a problem. If you try to connect to http://blog.example.com
you’ll get the aforementioned error message, telling you that “127.0.0.1:4321,
is not recognized”. It makes sense if you think about it, Nginx will redirect
the connection to 127.0.0.1:4321
so Keter can handle it, but there is no
application that responds to 127.0.0.1:4321
, and notice the port number here,
as it is significant for Keter when trying to find a corresponding
application.
To fix this, we must allow our application to respond to 127.0.0.1:4321
as
well:
hosts:
- blog.example.com
- blog.example.com:4321
- 127.0.0.1:4321
Restart Ngnix and Keter on the server to allow the configuration to take effect and redeploy the application:
$ stack -- exec yesod keter
If you’re going to use the redirect
stanza to automatically redirect any
connection to, lets say example.com
to wwww.example.com
:
- type: redirect
hosts:
- example.com
actions:
- host: www.example.com
Then be completely sure to have www.example.com
in the hosts
option of the
webapp
stanza as well, failing to do this will take you to the same error
message.
If you’re still reaching this error:
Unfortunately, the same error message appears if an application that responds to that host is actually found, but is failing to start.
Check the /opt/keter/log/app-yourapp/current.log
log file, chances are you
have changes in your persistent models that can’t be reflected in your database
without user intervention, so be sure to manually fix them the same way you have
to do in your development database.
It is pretty common to forget changes in persistent models that need manual user intervention after deploying, similarly to the error above, this will prevent the app to start. If you currently have a version of your app working, Keter will use it instead of the new one if it fails to start, so if the latest deployed changes seem to not be taking effect, this can also be the source of the problem.
]]>Here I’d like to talk about the issues of Java as a language. Although these are a significant part of what’s wrong with Java, keep in mind that it’s only half of the equation, the other part being its surroundings.
Java is supposed to be a pointers-free language, unlike those pesky C and C++. Pointers, although very powerful, are a low level construct that should not be present in a higher level language. Most of the time We want to be as far away as possible from pointers when programming unless lower level memory access is specifically needed.
The problem is Java does have pointers; Moreover, it manages to keep most of the inconveniences of having pointers while giving none of the benefits of not having them. If you’re not giving me the power of pointers, at least be kind enough to remove the problems they induce.
Java loves to call pointers by the nickname of “references”, which is only a way to pretend that there are no pointers.
Java makes everything a pointer, thus the heavy usage of the new
keyword as a
way to create a reference. Having this references gives the JVM the ability
to manage memory with the Garbage Collector of course, but it comes with
negative consequences for the programmer.
The book Elegant Objects by Yegor Bugayenko says:
In a nutshell, you’re making a big mistake if you use NULL anywhere in your code. Anywhere – I mean it.
And I completely agree with that. The problem here is having to take into account the possibility of NULL in a high level language that supposedly doesn’t have pointers and tries to hide those details from you in the first place.
In C or C++, when you dereference a NULL pointer, you get a Segmentation Fault and your program crashes. In Java, when you try to use a NULL reference you get a NullPointerException and your program crashes as well. So what gives?
You may say that the sources of these crashes are different, the Segmentation Fault comes from the OS trying to stop you from crashing the entire system, while the NullPointerException comes from the JVM that… Well, has nothing left to do but crash. I don’t see how is that any better.
NullPointerExceptions are terribly common in Java, and you have to hunt them down just as any null pointer dereference bug. And if you’re thinking the actual benefit of this is having the GC taking care of the memory instead of having to remember to manually free memory, then think again, as there are languages that take care of that without a GC, including C++.
Tony Hoare himself calls NULL the “Billion-Dollar mistake”.
So java is cluttered with pointers, useless pointers. In C/C++ pointers are one of the most powerful constructs, they allow you to get closer to the machine and control its actions with scalpel precision; In Java you get your programs to crash due to NULL pointers while getting no benefit in exchange.
Pointers in Java percolate up in even more creative ways, take for instance
the Equality comparison problem: When you perform equality comparison ==
what
you’re actually comparing is pointer equality, not value equality for which
you need a special method equal()
, this is a low level language trait that,
unlike other low level languages, won’t put the power on the programmers hand
but just the burden.
Java has a lot of additional traits that make it not only a low level language in disguise, but also in my opinion a bad language in general.
Most of the Java ugly verbosity is attributed to its static, strong typing discipline that forces you to annotate the types of everything, everywhere. But this is not the type discipline fault.
In Java, you declare, for instance, a vector of integers:
Vector<Integer> vector = new Vector<Integer>();
Or a vector of vectors of integers:
Vector<Vector<Integer>> vector = new Vector<Vector<Integer>>();
And it gets progressively uglier like that. Java’s way of dealing with this to
some extent, is the empty diamond operator <>
, so instead we could write:
Vector<Vector<Integer>> vector = new Vector<>();
But that’s pretty much as far as it gets. C++11 on the other hand has the
auto
keyword to let the compiler do what compilers do best: mechanical,
repetitive, deterministic tasks; Type inference is one of those tasks.
Every time, everywhere a time annotation is needed, you provide one only if it’s
necessary to avoid ambiguity, otherwise, just use auto
and let the compiler do
it for you.
One of the main Java selling points is Memory Safety, you see, in C you have
to free your memory with free()
in the right place, at the right time after
every memory allocation with malloc()
and friends. If you forget to free your
memory you’ll have memory leaks, if you free it twice, or if you free it at the
wrong time you’ll have a segmentation fault.
Java on the other hand leverages the Garbage Collector to do it for you, the problem is, this works for memory only!
Whenever you initialize a socket, or a database connection, or open a file, you still need to close it at the right time; So you still can and will have resources leakage.
C++ solves all of those problems beautifully by using Resource Acquisition Is
Initialization or RAII for
short. And by the way, if you hit the same kind of problems you face in C with
malloc()
and free()
but with C++’s new
and delete
, then you’re doing
it wrong.
By using C++’s RAII mechanisms you’ll never have to remember to free memory, close files, sockets, database connections or anything else. Java is supposed to be a higher level language than C++.
C++ and many other imperative and OOP languages suffer form the Exceptions driven programming issue as well, but Java manages to screw it up even further.
The heavy use of exceptions forces the programmer to write tons of:
try {
...
}
catch(someExcetption e) {
...
}
catch(someOtherExcetption e) {
...
}
catch(yetAnotherExcetption e) {
...
}
The usual alternative is just:
try {
...
}
catch(Excetption e) {
System.out.println("An exception has occurred, sorry ¯\_(ツ)_/¯");
}
The result of this is that the code that matters, the actual logic we’re trying to encode in the program gets deeply buried, making it hard to read, hard to understand, hard to maintain, hard to modify and awfully ugly. Although most languages suffer from a variant of this issue, some other languages handle it gracefully by encoding the possibility of failure in the type system.
Most programming languages break equational reasoning, but that’s pretty common; Exceptions go further by even breaking the imperative sequentiality (cough GOTO cough).
As mentioned in a previous post: No, not everything is an object. OOP has a lot of problems on its own, and it deserves its own post, but here I’m talking about the way Java enforces OOP.
Most OOP languages have this paradigm as a feature, but still allow for free functions, free data and so on. The problem with Java being strictly OOP is that it forces objects even when they don’t fit, even when they adversely affect composition, modularity or readability.
The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. – Joe Armstrong
In most languages you can perform actions, but in Java, having objects as the only mean of abstraction you must have “actioners” to perform actions, and must force them into existence to do anything even if it convolutes your code and logic. OOP is usually bad in general, although useful in certain contexts; Java makes it so that everything that is wrong with OOP is also the only way.
Those and more are the common pains of Javaland, that Steve Yegge describes wonderfully in Execution in the Kingdom of Nouns.
Java is both fast and slow, depending on what language you compare it with. When you compare it with higher level languages, Java is reasonably faster, but when you compare it with C or C++, Java is miserably slow and heavy on resources.
Taking into account that Java is more of a low level language rather than a high level one as we have seen, it should be compared to its closes cousins C and C++, in which case you inevitably conclude that it’s just slow, very slow.
As we’ve seen, Java is mostly a low level programming language that doesn’t really provide the benefits of one, while it pretends to be a high level language and fails miserably.
This leads to the current situation:
| C | C++ | Rust | Java | Ruby | Python | PHP | Perl | Earlang | OCaml | Haskell
|--- Low Level --| ??? |--- High Level ---|
From the low level languages extreme, Java can perfectly be replaced by C++,
Rust and others. Both of these languages provide low level capabilities, and are
good for systems programming, while providing better high level traits like
C++’s RAII or Rust’s statically guaranteed safety. Both of these languages will
avoid Java’s NullPointerException
and resource leaks.
From the high level languages extreme, Java can be replaced by pretty much any other language. Almost any of them will provide a nicer syntax, better and more powerful ways of abstraction, better terseness, better tooling, better everything.
This makes Java completely replaceable by any other language, it serves no particular purpose and is particularly good at nothing.
Professors in computer science Robert B.K. Dewar and Edmond Schonberg, published an article in the “Journal of Defense Software Engineering” discussing how Java is a bad programming language for CS education, and how it produces programmers that are incapable of doing actual problem-solving. Or, as Joel puts it in his article “The Perils of JavaSchools”:
]]>I’ve seen that the 100% Java schools have started churning out quite a few CS graduates who are simply not smart enough to work as programmers on anything more sophisticated than Yet Another Java Accounting Applications
Our .cassius
files will live inside the css
directory, together with a
Gen.hs
Haskell module that will take the Cassius sources and compile them to
CSS:
{-# LANGUAGE TemplateHaskell #-}
module Gen where
import Text.Cassius
import Data.Text.Lazy (unpack)
main :: IO ()
= do
main writeFile "default.css" $ unpack $ renderCss def
writeFile "post.css" $ unpack $ renderCss post
writeFile "post-list.css" $ unpack $ renderCss postList
= $(cassiusFile "default.cassius") ()
def = $(cassiusFile "post.cassius") ()
post = $(cassiusFile "post-list.cassius") () postList
This module, when executed (runhaskell Gen.hs
), will compile the Cassius
sources default.cassius
, post.cassius
and post-list.cassius
to the
corresponding CSS files that the -untouched- CSS rule in site.hs
will
take and use in the generated site.
The Cassius compilation doesn’t happen when we stack exec site build
, as we
haven’t defined a rule, nor a compiler for them in site.hs
and we won’t,
because the Template Haskell requirements mess things up.
So instead we are going to have a Makefile
that will watch for changes in all
the css/*.cassius
files and perform the recompilation by executing Gen.hs
:
.PHONY: build test css
build: css
stack build
stack exec site rebuild
css:
cd css && stack runhaskell Gen.hs
watch:
while true; do make css; inotifywait -qre close_write css/*.cassius; done
This way, we can execute make watch
and it will recompile the Cassius files
when needed. A normal stack exec site watch
can be running alongside to take
care of everything else.
Silly Bytes went through its first 5 years of existence hosted on Google’s Blogger service, and it did well. Although Blogger offers a fair amount of flexibility, you can’t have total control over it, and having to write posts with the built-in WYSIWYG interface or pasting the HTML output is the biggest pain point of it. I solved most of that by writing a CLI tool that allows me to write posts offline in markdown, compile them, and deploy them from the terminal leveraging Blogger’s API. But that’s still too much of a flex.
In this post I’ll describe the process of porting an existing Blogger blog to Hakyll and GitHub pages using Silly Bytes itself as a case study.
So here is what I want instead:
Completely port Silly Bytes to Hakyll and GitHub pages. Write every post in markdown only, and have them automatically generated.
Further customize the design. While I’ve managed to get pretty far with Blogger’s custom CSS option, there are still some aspects that doesn’t quite fit what I want.
Preserve all the links to previous posts.
We’ll strive to keep the old blog completely functional till the last moment when we finally change where the domain name points to.
The GitHub pages naming
convections
state that, in order to create a dedicated repo for a personal or organizational
page, we must have a repository named user.github.io
or
organization.github.io
respectively, this way GitHub will read and serve any
index file in the repository root; This supposes a problem though, We want to
keep our generated site inside a directory to keep compiled files separated from
the sources.
There are a couple of solutions for this, but they all use some Git branches trickery, juggling with a CI service, or both; It feels way to hacky to me, not saying that my solution is better, but it just fits better with the work flow I’m looking for.
GitHub pages offers project specific pages as well, those are served from a
dedicated docs
directory on it, so this is what we’re going to use instead.
I’ve created a sillybytes
repository in the sillybytes
organization. Then in settings → GitHub Pages → Source
I’ve selected
master branch /docs folder
as the page source.
For the content of that repository, this will create the initial Hakyll scaffolding:
$ hakyll-init sillybytes
$ cd sillybytes
$ stack init
$ stack build
By default, Hakyll outputs the generated site in a _site
directory, but
GitHub pages will read the site from a docs
directory, so let’s fix that by
editing the site.hs
file.
The main
function in site.hs
uses the hakyll
function with the default
configuration, so we need to swap that with a custom one:
= hakyllWith config $ do
main ...
...
config :: Configuration
= Configuration
config = "docs"
{ destinationDirectory = "_cache"
, storeDirectory = "_cache/tmp"
, tmpDirectory = "."
, providerDirectory = ignoreFile'
, ignoreFile = "echo 'No deploy command specified' && exit 1"
, deployCommand = system . deployCommand
, deploySite = True
, inMemoryCache = "127.0.0.1"
, previewHost = 8000
, previewPort
}where
ignoreFile' path| "." `isPrefixOf` fileName = True
| "#" `isPrefixOf` fileName = True
| "~" `isSuffixOf` fileName = True
| ".swp" `isSuffixOf` fileName = True
| otherwise = False
where
= takeFileName path fileName
Here I’ve pretty much left the default configuration intact and only changed the
destinationDirectory
field to be docs
.
Now recompile and regenerate the site:
$ stack build
$ stack exec site rebuild
And the generated site will now be on docs
.
The deployment process consists of regenerating the site:
$ stack exec site rebuild
Committing the changes on docs
:
$ git add docs
$ git commit -m "Build"
And pushing:
$ git push origin master
No need for esoteric spells here.
It is imperative to preserve the links to previous posts that were originally published on Blogger, so they keep pointing to the right post.
Blogger paths convention is as follows:
Every post is on the corresponding year and month of publication name space
like year/month/post.html
. So we must preserve this structure at least for the
legacy posts.
In order to achieve this keep a legacy
directory inside posts
, that will in
turn contain a directory tree for every year and month when posts exist.
sillybytes/posts/legacy
|
+---2012
| |
| +----01
| | +---- post.md
| |
| +----02
| |
| +---- ...
|
|
+---2013
| |
| +----01
| |
| +----02
| |
| +---- ...
|
|
+--- ...
|
+----01
|
+----02
|
+---- ...
Then we need an additional rule in site.hs
"posts/legacy/**" $ do
match $ customRoute $ (flip replaceExtension "html") . joinPath
route . (drop 2) . splitPath . toFilePath
$ pandocCompiler
compile >>= saveSnapshot "content"
>>= loadAndApplyTemplate "templates/post.html" postCtx
>>= loadAndApplyTemplate "templates/default.html" postCtx
>>= relativizeUrls
This will ensure that the year/month/post.html
directory structure is
preserved on the resulting generated site.
From here, a pretty much manual porting process is required. Most of the legacy posts were originally published right in the Blogger interface, so some rewrite to markdow is needed.
The porting process is as follows:
posts/legacy
to preserve
the same year/month/post.html
path..md
extension.images
directory and
put all the post images in it.Any newer posts that are created after the porting can live in the posts
directory, there is no need to keep the year/month/post.html
scheme any more.
The only thing left is the actual migration by pointing the domain name to the new site.
At this point a bigger problem arises. Given that we are serving the blog from
sillybytes/docs
we’ll need a URL Redirect record pointing to
sillybytes.github.io/sillybytes
rather than a CNAME to just
sillybytes.github.io
. If you’re fine with that, then you’re done.
I really wanted a proper CNAME record though, so I had to change the setup a bit:
sillybytes
for the sources, and
sillybytes.github.io
for the generated page.docs
directory to the
sillybytes.github.io
repository.sillybytes.github.io
.The CLI tool I was using before for Blogger deployment is no longer useful, but I can still adapt it to the new deployment schema:
cp -rfv _site/* ../sillybytes.github.io/
cd ../sillybytes.github.io
display_info "Deploying..."
git add .
git commit -m "Deploy"
git push origin master
display_success "Deployed!"
As well as aliasing common Hakyll commands:
That’s some comfy blogging right there.
]]>Every post I write is currently a separate git repo hosted on the Silly Bytes GitHub organization. The post is written and maintained in Markdown using Pandoc and a convenient Makefile generated by the made script.
Writing posts in Markdown is nice but is not that advantageous if you still have to mess around with Blogger’s web interface, so here is the plan:
$ make
The first 3 steps are already covered so lets dig into the Blogger negotiation part.
Google’s APIs come in handy here, the best language option was Python (I refuse tu use Java). So starting from an example I came up with this helper script:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
from googleapiclient import discovery
from oauth2client import client
from oauth2client import file
from oauth2client import tools
import sys
import os
import httplib2
import argparse
="1318550761233559867"
SILLYBYTESID
def main(argv):
if (len(argv) < 3):
print("Post title must be provided as the first argument and html file as the second")
1)
exit(
= argv[1]
post_title = argv[2]
input_file
= 'https://www.googleapis.com/auth/blogger'
scope
= [tools.argparser]
parent_parsers
parent_parsers.extend([])= argparse.ArgumentParser(
parser =__doc__,
description=argparse.RawDescriptionHelpFormatter,
formatter_class=parent_parsers)
parents= parser.parse_args("")
flags
try:
= os.path.join(os.path.expanduser("~") + '/.sillybytes/',
client_secrets 'secrets.json')
except:
print("Can't find secrets.json file maybe?")
1)
exit(
= client.flow_from_clientsecrets(client_secrets,
flow =scope,
scope=tools.message_if_missing(client_secrets))
message
= file.Storage('auth_data' + '.dat')
storage = storage.get()
credentials if credentials is None or credentials.invalid:
= tools.run_flow(flow, storage, flags)
credentials
= credentials.authorize(http = httplib2.Http())
http = discovery.build('blogger', 'v3', http=http)
service
try:
= open(input_file, 'r').read()
content except FileNotFoundError:
print("Input file not found")
1)
exit(
= {
body "kind": "blogger#post",
"title": post_title,
"content": content
}
try:
= service.posts()
posts = posts.insert(blogId=SILLYBYTESID, body=body, isDraft=False)
request = request.execute()
result print("Live: " + result['url'])
except:
print("Can't execute request")
1)
exit(
if __name__ == '__main__':
main(sys.argv)
The script will initiate an OAuth negotiation when needed and store the
authentication tokens in the auth_data.dat
file.
Before we’re able to use this we need to create a new API project, configure
it and get the client_secrets.json
that the script will use to start the OAuth
negotiation.
First, enable the Blogger API at: https://console.developers.google.com/apis/library
Then create a new project, a new set of credentials and download the JSON file
from it. From the script above you’ll gather my client_secrets.json
file will
be located at ~/.sillybytes
.
Now, running:
$ python deploy.py "post title" "post HTML"
Will push the post to Blogger!
This is good enough already, we could just invoke deploy.py
from the Makefile,
but we can still do better:
$ silly help
Now I can create all the post boilerplate by doing silly new
and deploying the
current post with silly deploy
. Much better.
ARM has taken over the embedded world, they’re ubiquitous in smartphones, tablets, laptops, computers inside computers, cars, refrigerators, microwave ovens, monitors, printers, you name it.
Note: Be aware that ARM is an architecture that manufacturers can implement. Is a common mistake to think ARM is a microcontroller on itself, it is not.
ST Microelectronics’s implementation of ARM are the STM32 microcontrollers: inexpensive, powerful and with great free software/hardware support.
Various series are available: F0, F1, F2, …, F7. You can identify your chip series after the STM32 prefix, I’m using a board with the “STM32F103C8” chip, so the series is F1.
These chips are relatively inexpensive and widely available, often mounted in convenient development or breakout boards.
Individual chips can be bought from electronic stores like Digi-Key or Mouser. For the current purpose though, making your own PCB to mount them is quite inconvenient.
The other option is to get one of the nice development boards ST offers:
Although these are cheap and amazing, we can go even cheaper with some breakout boards available on Ebay and others. You can get a STM32F103 chip in a nice board for less than $ 5 USD.
STM32 chips are programmed using a ST-LINK device, which is an in-circuit debugger and programmer that interfaces with the chip using JTAG or Serial Wire Debugging (SWD). This is similar to the USBASP for AVR or the PICkit for PIC.
Development boards like the Nucleo include the st-link hardware right on the board, so you can connect it to a host computer using USB and program/debug the target chip without any additional external hardware.
If you’re using breakout boards (like the Ebay ones) or if you mounted a chip in a custom PCB, you will need an external st-link hardware. Fortunately they are also available for cheap on Ebay, or you can buy the official one for a few extra bucks if you prefer, they both will work exactly the same with the flashing software.
If you’re using an ST development board with the st-link built-in just connect it to your computer and you’re ready to go, but for breakout boards and a dongle st-link you’ll need to connect four wires to it:
WARNING: STM32 chips run on 3.3V, most breakout boards will include a voltage regulator, so it can be powered from USB, and st-link dongles will provide a 3.3V VCC PIN to power the chip. DON’T Connect the board to the PC using USB while the chip is powered up using the st-link programmer! Connect one or the other but not both simultaneously. The st-link dongle provides a 5V PIN as well, DON’T use it, the STM32 chips are not 5V tolerant, use the 3.3V PIN only.
ST-Link dongles have labeling on the front, just connect the right pins. On the board side, follow the labeling printed on the pins or use a pin out diagram. The connections for the st-link on the breakout board I’m using look like this:
You’ll need a compiler, a debugger, some utilities to manage your binaries and the necessary software to flash your firmware using the ST-LINK device (dongle or built-in):
You should be able to install them all of from your distribution repositories.
In case you can’t find stlink
on them, get it from the GitHub
repository.
The stlink
package provides these executables:
st-flash
- Write and Read a program from the target chipst-util
- Creates a GDB server, so you can load, run and debug a program on the target chipst-info
- Search and provides information about the st-link device and the target chipst-term
- Gives you log-like reports from the program on the target chipWith the hardware connected and the PC software installed we can try it out and see if everything is working. No example program yet though.
Connect your st-link device (connected to the breakout board) or your development board to the host PC using USB and run:
$ st-info --probe
You’ll get some neat information about the chip that is hooked up to the st-link device:
Found 1 stlink programmers
serial: 543f6a06663f505130531567
openocd: "\x54\x3f\x6a\x06\x66\x3f\x50\x51\x30\x53\x15\x67"
flash: 65536 (pagesize: 1024)
sram: 20480
chipid: 0x0410
descr: F1 Medium-density device
Fantastic! Everything is working fine, lets move on.
ARM provides a Cortex Microcontroller Software Interface Standard (SMSIS) as an abstraction layer for the ARM Cortex core to increase software portability. Think of it as a standard API that you can use to interface with ARM chips in a vendor independent way.
On top of that you might want to have a Hardware Abstraction Layer (HAL) to interface with the peripherals each particular chip provides (UART, USB, I2C, SPI, TIMERS, etc).
We have two options of libraries that provide those abstraction layers:
LibOpenCM3 uses the LGPL licence (which I prefer), and STM32Cube uses the lax BSD licence. Balau covered the licensing topic in more detail in his blog post.
ST provides the so called “STM32Cube”, which is a bundle of software and libraries for STM32 development. It contains a graphical software for basic C code generation, software layers of abstraction like HAL and middleware, software layers for built-in peripherals on ST’s development boards and examples.
The STM32Cube is available per chip series, so for development boards with STM32F4xx chips you’ll need the STM32CubeF4. I have a breakout board with the STM32F103C8 chip, so I would use the STM32CubeF1, you get the idea.
STM32Cube provides 3 layers:
Middleware software components like USB Host and Device libraries or FAT file system for SD cards interfacing
Graphical demonstration that uses the level 1 Middleware.
You can read more about it on the STM32Cube user manual. Here is the STM32CubeF1 manual to get you started.
LibOpenCM3 aims to provide a free (as in freedom) library for various ARM Cortex-M3 microcontrollers, including the STM32 chips.
Using this library is more or less straight forward, there are no (explicit) layers here. You can read more about it in the wiki. They have some fantastic Doxygen documentation for the API as well.
The LibOpenCM3 project provides very useful examples, lets use one of those as the first program. I’m Using the STM32F103C8T6, so I need the F1 series examples and libraries, adjust the steps to use the appropriate one for your chip/board.
Notice that the examples are organized to correspond to various development boards, but it doesn’t really matter, the reason for this is the distribution of LED’s and Push buttons in those boards, but as long as you’re using the same chip series you just need to pick up one and connect LED’s, buttons, etc in the right pins as needed. I’m going to use the examples for the “stm32-h103” board from Olimex, even though I’m using a breakout board from Ebay; The F1 is the important thing here.
$ git clone --recursive 'https://github.com/libopencm3/libopencm3-examples'
$ cd libopencm3-examples
$ make
$ cd examples/stm32
$ cd f1
$ cd stm32-h103/miniblink
This example will BLINK an LED connected to PIN 12 of the GPIO port C, but my chip doesn’t have it! No problem, I’m going to change it (you can use your favorite editor here):
$ vim miniblink.c
Now change all appearances of GPIOC
to GPIOB
so the program uses the GPIO
port B instead. (Use an available pin in your specific chip/board).
In Vim:
:%s/GPIOC/GPIOB
Save the file and compile:
$ make
Generate the binary:
$ arm-none-eabi-objcopy -O binary miniblink.elf miniblink.bin
Flash it:
$ st-flash write miniblink.bin 0x8000000
Connect an LED to the GND and PB12 pins through a 330 Ohm resistor and rejoice with it’s blinkiness.
You can also interface with the target device using GDB: Debug, Upload firmware, run, stop, set break points, etc. I’m going to assume you know how to use GDB and only going to explain how to upload the firmware from it.
Create a GDB server to interface with the connected target:
$ st-util -p 4444
Run ARM GDB:
$ arm-none-eabi-gdb
Connect to the server
(gdb) target extended-remote localhost:4444
Flash the firmware (notice we’re using the ELF file here not the BIN one):
(gdb) load miniblink.elf
Run the firmware:
(gdb) continue
You can stop it with C-c
.
Imagine you have a button that you can push, it will test your code and if everything is working a green light will come up, but if something is broken, a red light will come up […]
He was of course talking about TDD. It got me inspired to build this little tool.
Hardware schematics, firmware and host software is available in this GitHub repository. Along with information on how to compile and use.
This is a physical toy traffic light to be used with software development TDD (and testing in general) tools. It will not boost your productivity nor make you a better programmer or TDD practitioner, but it looks cool :)
The atmega328p AVR chip is very popular and cheap, so much so, that chances are high you got it with the Arduino bootloader, which gets in the way as we can perfectly use the internal oscillator at 1Mhz instead of an external 16Mhz crystal. Fix this by changing the fuses:
# avrdude -p m328p -c usbasp -U lfuse:w:0x62:m -U hfuse:w:0xd9:m
The circuit is simple enough to mount in some perfboard. Additionally, I added some small neodymium magnets in the back for mounting purposes.
The firmware is no more than some UART boilerplate with a 4800
baud rate so
that it’s stable at 1MHz.
#include <avr/io.h>
#include <util/delay.h>
#define F_CPU 1000000
#define BAUD 4800
#define BAUD_PRESCALE ((((F_CPU/16) + (BAUD/2)) / (BAUD)) - 1)
char getchar(void)
{
while ((UCSR0A & (1 << RXC0)) == 0) {}
return UDR0;
}
The main loop will wait for a command r
, y
or g
and turn on the pin
corresponding to the color LED.
#include <avr/io.h>
#include <util/delay.h>
#define F_CPU 1000000
#define BAUD 4800
#define BAUD_PRESCALE ((((F_CPU/16) + (BAUD/2)) / (BAUD)) - 1)
char getchar(void)
{
while ((UCSR0A & (1 << RXC0)) == 0) {}
return UDR0;
}
Notice how if any other character is received the output gets cleared.
The host software configures the serial port with a 4800
baud rate:
void serial_init(char* port)
{
if (COM_FD > 0)
{
return;
}
// Open serial port file
int fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
// Configure serial port
struct termios config;
if (tcgetattr(fd, &config) != 0)
{
= -1;
COM_FD ("Error while configing serial port");
printf(1);
exit}
(&config, B4800);
cfsetispeed(&config, B4800);
cfsetospeed
.c_cflag |= (CLOCAL | CREAD | CS8);
config.c_cflag &= ~(PARENB | PARODD);
config.c_iflag = 0;
config.c_oflag = 0;
config.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
config.c_cc[VTIME] = 5;
config.c_cc[VMIN] = 0;
config
if (tcsetattr(fd, TCSANOW, &config) != 0)
{
= -1;
COM_FD ("Error while configing serial port");
printf(1);
exit}
= fd; COM_FD
With that, controlling the LEDs is as simple as:
(COM_FD, "r", 1); write
Find more information about how to use it in the GitHub repository.
]]>This is what we expect to accomplish:
Most of this functionality is achieved by using already available tools and already available Vim plugins for those tools. So I’ll assume you have your way to install the plugins (I’m using vim-plug).
Here is my complete .vimrc.
Important: Every line of vimrc used should be enclosed in an :h :augroup
:
augroup ft_haskell
au!
...
augroup END
The neco-ghc plugin declares a complete
omnifunction. Use it by defining the local omnifunc
:
au FileType haskell setlocal omnifunc=necoghc#omnifunc
I’ve contributed the GHC compiler plugin to upstream Vim recently, but it may take a while before you get the latest vim runtime from your distribution. So in the meantime you can install it like any other plugin from the GitHub repository here: https://github.com/alx741/ghc.vim. Update: It’s been merged into vim’s runtime, you should have it by default now.
Then load it for the Haskell filetype in you vimrc:
au FileType haskell compiler ghc
Taking advantage of vim 8 asynchronous job control using the asyncrun.vim plugin, we can define some convenient mappings for building and testing using Haskell stack:
au FileType haskell setlocal makeprg=stack
au FileType haskell nnoremap <buffer> gj :write<CR> :exec "AsyncRun " . &makeprg . " build"<CR>
au FileType haskell nnoremap <buffer> gk :write<CR> :exec "AsyncRun " . &makeprg . " test"<CR>
After running one of those the results will be loaded into the quickfix list.
There are plugins that offer much more tight integration but for me, it is enough to start GHCI from the current vim instance in a Tmux pane loaded with the current project or Haskell source, so taking advantage of the vimux Tmux integration plugin, lets define a function:
function! RunGhci(type)
call VimuxRunCommand(" stack ghci && exit")
if a:type
call VimuxSendText(":l " . bufname("%"))
call VimuxSendKeys("Enter")
endif
endfunction
And some mappings:
au FileType haskell nmap <silent><buffer> <leader>gg :call RunGhci(1)<CR>
au FileType haskell nmap <silent><buffer> <leader>gs :call RunGhci(0)<CR>
So doing \gg
will start a GHCI session loaded with the current file and \gs
will load a GHCI session for the current stack project.
Vim uses K
(upper case k) to look up a keyword under the cursor, so we can
leverage that and just define the right keywordprg
:
au FileType haskell set kp=hoogle
Or, if you prefer having your results within Vim, you can use the
[vim-hoogle](https://github.com/Twinside/vim-hoogle] plugin, and remap K
:
au FileType haskell nnoremap K :HoogleInfo<CR>
When editing a function’s arguments we would like to have a text object so doing
cia
(change inner argument) or daa
(delete all argument) will work; These
will to the trick:
au FileType haskell onoremap <silent> ia :<c-u>silent execute "normal! ?->\r:nohlsearch\rwvf-ge"<CR>
au FileType haskell onoremap <silent> aa :<c-u>silent execute "normal! ?->\r:nohlsearch\rhvEf-ge"<CR>
In order to easily jump between functions we could define a function:
function! JumpHaskellFunction(reverse)
call search('\C[[:alnum:]]*\s*::', a:reverse ? 'bW' : 'W')
endfunction
And some mappings such that [[
or ]]
will take us to the previous and next
function:
au FileType haskell nnoremap <buffer><silent> ]] :call JumpHaskellFunction(0)<CR>
au FileType haskell nnoremap <buffer><silent> [[ :call JumpHaskellFunction(1)<CR>
Let’s add some extra convenience and use gI
for jumping to the first import
statement and gC
to edit the .cabal file:
au FileType haskell nnoremap <buffer> gI gg /\cimport<CR><ESC>:noh<CR>
au FileType haskell nnoremap <buffer> gC :e *.cabal<CR>
ghc-mod is the Happy Haskell Programming package! With a bunch of functionality, here we will be using just a few:
You need the ghc-mod package: stack install ghc-mod
and the ghcmod-vim
plugin.
au FileType haskell nnoremap <silent><buffer> git :GhcModTypeInsert<CR>
au FileType haskell nnoremap <silent><buffer> gfs :GhcModSplitFunCase<CR>
au FileType haskell nnoremap <silent><buffer> gtt :GhcModType<CR>
git
(g insert type) will insert the missing type declaration of an
expression, take for instance this Haskell code:
module Hello where
Just a) = Left a
f (Nothing = Right () f
With the cursor in the first f
(the function name) using the tt
mapping will
produce:
module Hello where
f :: Maybe a -> Either a ()
Just a) = Left a
f (Nothing = Right () f
Neat!, go ahead and play around with the other mappings, you won’t be disappointed.
By default, Neomake will use hlint on
the current file when the :Neomake
command is invoked on a Haskell source
file, so by adding a mapping:
au FileType haskell nnoremap <buffer> gll :Neomake<CR>
gll
will open the location list with the lints, which takes us to some
convenience mappings:
au FileType haskell nnoremap <buffer><silent> gl<space> :call ToggleLocationList()<CR>
au FileType haskell nnoremap <buffer><silent> glc :sign unplace *<CR>
So now is possible to toggle the location list with gl<space>
and clear it
with glc
.
You will need the Stack tool of course, and hlint that you can install with
stack install hlint
.
Hindent allows beautifying Haskell code, you could use it by setting the
formatprg
option and then trigger it with the =
command, but there is a
problem: if your code happens to have any syntax errors, it will be replaced
with a nasty error message. To handle this we’re going to use the
vim-hindent plugin instead, so each
time we save a Haskell source file it will be automatically beatified.
Don’t forget to configure it:
let g:hindent_on_save = 1
let g:hindent_line_length = 80
let g:hindent_indent_size = 4
One extra thing left is to align stuff in the code so it looks nicer
au FileType haskell nmap <silent><buffer> g<space> vii<ESC>:silent!'<,'> EasyAlign /->/<CR>
Take for instance this simple example for the sake of the argument:
module Test where
f :: Int -> String
= case x of
f x 1 -> "1"
2 -> "2"
3 -> "3"
Using g<space>
we got:
module Test where
f :: Int -> String
=
f x case x of
1 -> "1"
2 -> "2"
3 -> "3"
So much better!
In Haskell, operators like ->
and =>
are very common, and I find it
cumbersome to type them manually. Let’s define a function:
function! Make_arrow(type)
if a:type
if (matchstr(getline('.'), '\%' . col('.') . 'c.') ==? ' ')
exe "norm! a-> "
else
exe "norm! a -> "
endif
exe "startreplace"
else
if (matchstr(getline('.'), '\%' . col('.') . 'c.') ==? ' ')
exe "norm! a=> "
else
exe "norm! a => "
endif
exe "startreplace"
endif
endfunction
And some insert mode mappings:
au FileType haskell inoremap <buffer> ;; <ESC>:call Make_arrow(1)<CR>
au FileType haskell inoremap <buffer> ;: <ESC>:call Make_arrow(0)<CR>
So while in insert mode typing ;;
or ;:
will insert ->
or =>
respectively. Additionally, it will avoid duplicated spaces between the types
and the arrows.
Maybe I’m a terrible typist, but writing the first upper case letter of the most common types hurts my pinkie. So by using some insert mode abbreviations:
au FileType haskell inoreab <buffer> int Int
au FileType haskell inoreab <buffer> integer Integer
au FileType haskell inoreab <buffer> string String
au FileType haskell inoreab <buffer> double Double
au FileType haskell inoreab <buffer> float Float
au FileType haskell inoreab <buffer> true True
au FileType haskell inoreab <buffer> false False
au FileType haskell inoreab <buffer> maybe Maybe
au FileType haskell inoreab <buffer> just Just
au FileType haskell inoreab <buffer> nothing Nothing
au FileType haskell inoreab <buffer> io IO ()
Now I can type all lower case without having to bother with the shift key and the capitalized version will be inserted instead.
Some neat integration with Yesod can be achieved by using the vim-yesod plugin which, by default, it gives you some mappings:
gh
- Jump to the handler of the route under the cursor in the config/routes
file.
gH
- Create a new handler for the route under the cursor in the
config/routes
file.
gm
- Jump to or create the i18n message under the cursor in a template file.
vim-yesod gives you config/routes
, config/models
and i18n messages/
syntax highlighting, but it doesn’t support shakesperean templates syntax so be
sure to install the
vim-syntax-shakespeare as
well.
He dejado de usar microcontroladores PIC por los motivos explicados aquí, pero voy a dedicar este post para escribir y explicar un programa sencillo escrito en ensamblador para el PIC16F876A.
El objetivo es el siguiente:
Se pretende usar el microcontrolador para llevar a cabo la conversión analógica-digital de una tensión variable (un LDR o un potenciómetro por ejemplo) y transmitir el resultado usando la UART. Además debe ser posible recibir por la UART un byte que debe alterar la configuración del Conversor Análogo Digital (DAC) interno del microcontrolador para, por ejemplo, cambiar el canal de entrada de la señal analógica o modificar la velocidad del reloj de conversión.
El código ha sido ensamblado con el “ensamblador de GNU” (gpasm) del juego de herramientas gputils, pero debería ser perfectamente compatible con las herramientas MPLAB de Microchip. En cualquier caso, la explicación y el 99% del código debería ser útil sin modificación alguna.
Este post se debería leer en paralelo junto con el datasheet del microcontrolador en cuestión PIC16F876A que se puede encontrar aquí: http://ww1.microchip.com/downloads/en/DeviceDoc/39582C.pdf
El código completo se encuentra aquí.
Empezamos examinando y explicando el código:
list p=16f876A
La primera linea le dirá al ensamblador los mapas de memoria que el enlazador deberá usar.
; Declaración de direcciones de memoria
; Datasheet pagina 17, figura 2-3
PORTA EQU 0x05
PORTB EQU 0x06
TRISA EQU H'85'
TRISB EQU H'86'
TRISC EQU H'87'
RP0 EQU H'05'
RP1 EQU H'06'
STATUS EQU H'03'
DATO EQU H'21'
ADCON0 EQU H'1F'
ADCON1 EQU H'9F'
PIR1 EQU H'0C'
INTCON EQU H'0B'
PIE1 EQU H'8C'
ADRESH EQU H'1E'
ADRESL EQU H'9E'
SPBRG EQU H'99'
TXSTA EQU H'98'
RCSTA EQU H'18'
TXREG EQU H'19'
RCREG EQU H'1A'
OPTION_REG EQU H'81'
IRP EQU H'07'
En el datasheet, pagina 17, figura 2-3 se puede encontrar el mapa completo de
memoria del microcontrolador. En estas lineas declaramos los nombres y
direcciones (en hexadecimal) de los mismos, para usarlos en el código con más
facilidad. El mnemónico EQU
asigna el nombre de la izquierda al valor de la
derecha. Para declarar un valor hexadecimal se usa el prefijo 0x
.
INIT
org 0
; Selección BANCO 1
; Datasheet pagina 16, sección 2.2
BSF STATUS,RP0
BCF STATUS,RP1
el mnemónico INIT
es la declaración de una etiqueta (la misma que se puede
cambiar por cualquier palabra que se desee), es el nombre con el cual nos vamos
a referir a esta sección de código desde otras partes del programa.
La directiva org 0
indica al enlazador que el código a continuación deberá ser
colocado desde la dirección 0 de la memoria de programa.
Las instrucciones BSF STATUS,RP0
y BCF STATUS,RP1
hacen un cambio al banco
de memoria 1. La memoria del microcontrolador está dividida en bancos y es
necesario cambiarnos al banco donde reside el registro que queremos modificar
en cada momento.
;;; Configuración de puertos IO
;;; Datasheet pagina 41
; El puerto A es de entrada
MOVLW B'00111111'
MOVWF TRISA
; El puerto B es de salida
MOVLW B'00000000'
MOVWF TRISB
; Puerto C: pin TX es salida, pin RX es entrada
MOVLW B'10001111'
MOVWF TRISC
La instrucción MOVLW
se usa para mover un valor literal al registro de trabajo
W
.
La instrucción MOVWF
se usa para mover el valor que se encuentra en el
registro de trabajo W
a un registro.
De esta forma, para colocar un valor arbitrario en un registro es necesario
colocarlo primero en el registro de trabajo W
usando la instrucción MOVLW
y luego moverlo al registro deseado con la instrucción MOVWF
.
Para indicar que el valor usado es binario se usa como prefijo una B
.
El puerto A
contiene los pines del conversor ADC, por lo que se configuran
como entradas. El puerto B
se configura como salida para, opcionalmente,
colocar LEDs que sirvan como indicadores visuales. El puerto C
contiene los
pines TX
y RX
usados para la comunicación UART, de forma que se configuran
para salida y entrada respectivamente.
;;; Configuración de puerto ADC
; Todas las entradas son analógicas
; Datasheet pagina 128
MOVLW B'10000000'
MOVWF ADCON1
La configuración del conversor ADC será recibida usando la comunicación UART, sin embargo, es necesario configurar de antemano qué pines serán analógicos y qué pines serán digitales. No usaremos pines digitales en este puerto, así que se configuran todos como analógicos según la tabla de la pagina 128 del datasheet.
La comunicación serial UART puede usarse para comunicar el microcontrolador con una computadora u otro dispositivo que a su vez se puede usar para comunicar con un teléfono inteligente. El dispositivo con el que se comunique es irrelevante para este post y el código es el mismo en cualquier caso.
Nótese que los registros que se configuran se encuentran en bancos distintos con lo cual es necesario hacer el cambio de banco en cada paso.
;;; Configuración UART
; Banco 1
BSF STATUS,RP0
BCF STATUS,RP1
; 19200 Baudios
; Datasheet pagina 114, tabla 10-4
MOVLW .12
MOVWF SPBRG
El registro SPBRG
o “Generador de baudios” recibe un valor (listado en la
tabla) dependiendo de la velocidad a la cual nos queremos comunicar, de la
frecuencia a la que se use el microcontrolador y el porcentaje de error que
estamos dispuestos a tolerar en la comunicación. Dada la frecuencia de un reloj
de 4Mhz usado y la necesidad de una comunicación a 19200 Baudios, la tabla
indica usar un valor decimal de 12
. Para indicar que el valor usado es decimal
se usa como prefijo un punto .
.
; Registro de transmisión
MOVLW B'10100100'
MOVWF TXSTA
El registro TXSTA
de la pagina 111 se configura con los valores adecuados para
configurar una comunicación de 8 bits de alta velocidad, asíncrona y para
activar los mecanismos de transmisión.
; Banco 0
BCF STATUS,RP0
BCF STATUS,RP1
; Registro de recepción
MOVLW B'10010000'
MOVWF RCSTA
BSF RCSTA,4
El registro RCSTA
(en el banco 0) de la pagina 112 se configura para una
comunicación de 8 bits, asíncrona y se activan los mecanismos de recepción.
El programa principal deberá esperar a que un byte para configurar el conversor
ADC llegue por la UART, tomar un valor de tensión y llevar a cabo la conversión
para finalmente transmitir el resultado por la UART, enviando primero el byte
bajo ADRESL
y luego el byte alto ADRESH
.
;;; Esperar primer byte de configuración
ESPERAR_CONFIG
BTFSS PIR1,5
GOTO ESPERAR_CONFIG
El pin numero 5
del registro PIR1
indicará que un dato ha llegado por la
UART.
La instrucción BTFSS
verificará el bit numero 5
del registro PIR1
y se
saltará la siguiente instrucción si el bit es igual a 1
. De esta forma
mientras no llegue el dato necesario la instrucción GOTO
se ejecuta y el
microcontrolador se queda en un bucle, pero cuando un dato es recibido la
instrucción GOTO
es saltada y el programa puede continuar.
; Colocar byte recibido en la configuración ADCON0 del conversor ADC
BCF STATUS,RP0
BCF STATUS,RP1
MOVF RCREG,W
MOVWF ADCON0
; Vaciar el bit de recepción
BCF PIR1,6
El registro RCREG
contiene el dato recibido por la UART, el cual se coloca en
el registro de trabajo W
para luego llevarse al registro de configuración
ADCON0
del conversor ADC. Así el conversor quedará configurado con el canal y
velocidad que se haya indicado en el dato que recibió y se puede proceder a la
conversión. Usando la instrucción BCF
se vacía el contenido del bit numero 6
del registro PIR1
para indicar que hemos leído el dato recibido.
;;; Esperar tiempo de adquisición e iniciar conversión
CONVERTIR
; Instrucciones de espera
NOP
NOP
NOP
NOP
NOP
Antes de realizar la conversión es necesario esperar un tiempo para que el
microcontrolador pueda recoger el valor de tensión en el pin, acorde a la pagina
129 del datasheet. Se puede lograr esto usando la instrucción NOP
, aunque
sería más adecuado usar un bucle que espere un tiempo más prudente, pero se
mantiene de esta forma por simplicidad.
; Activar conversor
BSF ADCON0,2
Activando el bit numero 2
del registro ADCON0
usando la instrucción BSF
inicia la conversión.
ESPERAR_CONVERSION
BTFSS PIR1,6
GOTO ESPERAR_CONVERSION
BCF PIR1,6
La conversión toma tiempo, por lo que se entra en un bucle hasta que el bit
numero 6 del registro PIR1
indique que se ha finalizado.
; Transmitir el resultado mediante la UART
TRANSMITIR_RESULTADO
BSF STATUS,RP0
BCF STATUS,RP1
; Transmitir byte bajo del resultado (ADRESL)
MOVF ADRESL,W
BCF STATUS,RP0
BCF STATUS,RP1
MOVWF TXREG
BSF STATUS,RP0
BCF STATUS,RP1
El resultado de la conversión se encuentra repartido en dos bytes: ADRESL
y
ADRESH
.
Colocamos el byte ADRESL
en el registro de trabajo W
para luego colocarlo en
el registro TXREG
, lo cual causará que sea transmitido usando al UART.
; Esperar que el primer byte se transmita
ESPERAR_1
BTFSS TXSTA,1
GOTO ESPERAR_1
BCF STATUS,RP0
BCF STATUS,RP1
; Transmitir byte alto del resultado (ADRESH)
MOVF ADRESH,W
MOVWF TXREG
BSF STATUS,RP0
BCF STATUS,RP1
; Esperar que el segundo byte se transmita
ESPERAR_2
BTFSS TXSTA,1
GOTO ESPERAR_2
BCF TXSTA,1
El bit numero 1
del registro TXSTA
indica que el dato se ha transmitido.
Esperamos en un bucle hasta que el byte bajo termine de ser transmitido y podemos repetirlo para el byte alto.
GOTO CONVERTIR
END
Finalmente se salta a la etiqueta CONVERTIR
para convertir y transmitir datos
infinitamente. El programa se termina con la directiva END
.
The window_select.sh script will do the trick using FZF
function fzf_select
{
pattern=$(ratpoison -c "prompt > ")
if [[ "$pattern" == "" ]];
then
exit 0
fi
window_list=$(ratpoison -c "windows %c")
selected=$(echo "$window_list" | fzf -q "$pattern" -1 -0)
if [[ "$selected" != "" ]];
then
ratpoison -c "select $selected"
else
ratpoison -c "echo [!] There is no a matching window for \"$pattern\""
fi
}
case $1 in
'ratmen')
ratmen_select
;;
'fzf')
fzf_select
;;
esac
This will use Ratpoison to prompt for a fuzzy string and will take you immediately to the matched window.
In order to invoke this, a Ratpoison mapping is required:
bind w exec window_select.sh fzf
]]>TL;DR*: Un corto y denso tutorial para aprender Haskell.
Asómbrate con Haskell. De verdad pienso que todos los desarrolladores deberían aprender Haskell. No creo que todos necesitan convertirse en ninjas de Haskell, pero deberían al menos descubrir que es lo que Haskell tiene para ofrecer. Aprender Haskell abre tu mente.
Los lenguajes comunes comparten los mismos fundamentos:
Haskell es muy diferente. El lenguaje usa muchos conceptos que nunca he escuchado antes. Muchos de esos conceptos te ayudarán a convertirte en un mejor programador.
Pero aprender Haskell puede ser difícil. Lo fue para mi. En este artículo intentaré proveer lo que me faltó durante mi aprendizaje.
Este artículo será ciertamente difícil de seguir. Esto es intencional. No hay atajo alguno para aprender Haskell. Es difícil y retador. Pero creo que es algo bueno. Debido a que es difícil es que Haskell es interesante.
El método convencional de aprender Haskell es leer dos libros. Primero “Learn You a Haskell” y justo después “Real World Haskell”. También pienso que esta es la forma correcta. Pero aprender de que se trata Haskell, deberás leerlos en detalle.
En contraste, este artículo es un resumen muy breve y denso de los principales aspectos de Haskell. También he agregado información que a mi me faltó mientras aprendía Haskell.
El artículo contiene cinco partes:
.hs
(Haskell), y por eso en los
ejemplos se escribe la ejecución de los mismos como $ runhaskell algo.hs
pero el nombre puede ser cualquiera.Herramientas:
ghc
: Compilador similar a gcc para C
.
ghci
: Haskell interactivo (REPL)
runhaskell
: Ejecutar un programa sin compilarlo. Conveniente pero muy lento
comparado a programas compilados
Muchos libros/artículos sobre Haskell empiezan por introducir alguna formula esotérica (quick sort, Fibonacci, etc…). Yo lo haré justamente al revés. Al principio no mostraré ningún super poder de Haskell. Empezaré por las similaridades entre Haskell y otros lenguajes de programación. Saltemos al “Hola Mundo” obligatorio.
= putStrLn "Hola Mundo!" main
Para ejecutarlo, puedes guardar el código en un fichero hola.hs
y:
$ runhaskell ./hola.hs
Hola Mundo!
Ahora, un programa que pregunte tu nombre y responda “Hola” usando el nombre ingresado:
= do
main print "Cuál es tu nombre?"
<- getLine
name print ("Hola " ++ name ++ "!")
Primero, comparemos esto con programas similares en algunos lenguajes imperativos:
# Python
print "What is your name?"
= raw_input()
name print "Hello %s!" % name
# Ruby
puts "What is your name?"
= gets.chomp
name puts "Hello #{name}!"
// In C
#include <stdio.h>
int main (int argc, char **argv) {
char name[666]; // <- An Evil Number!
// What if my name is more than 665 character long?
("What is your name?\n");
printf("%s", name);
scanf("Hello %s!\n", name);
printfreturn 0;
}
La estructura es la misma, pero hay diferencias en la sintaxis. La parte principal de este tutorial será dedicada a explicar por qué.
En Haskell hay una función main
y todo elemento tiene un tipo. El tipo de
main
es IO ()
. Esto significa que main
causará efectos
secundarios.
Solamente recuerda que Haskell puede lucir mucho como los lenguajes imperativos populares.
Antes de continuar debes ser advertido sobre algunas propiedades esenciales de Haskell.
Funcional
Haskell es un lenguaje funcional. Si tienes experiencia con lenguajes imperativos, deberás aprender muchas cosas nuevas. Con suerte muchos de estos nuevos conceptos te ayudarán a programas incluso en lenguajes imperativos.
Tipado estático inteligente
En lugar de meterse en tu camino como en C
, C++
o Java
, el sistema de
tipos está aquí para ayudarte.
Pureza
Generalmente tus funciones no modificarán nada en el mundo exterior. Esto significa que no pueden modificar el valor de una variable, no pueden obtener entrada del usuario, no pueden escribir en la pantalla, no pueden lanzar un misil. Por otro lado, el paralelismo será muy fácil de lograr. Haskell hace deja claro donde los efectos secundarios pueden ocurrir y donde el código es puro. También, será mucho más fácil razonar sobre el programa. La mayoría de los errores serán prevenidos en las partes puras del programa.
Además, las funciones puras siguen una ley fundamental en Haskell:
Aplicar una funcion con los mismos parámetros siempre producirá los
mismos valores.
Perezoso (laziness)
Laziness por defecto es un diseño de lenguaje muy poco común. Por defecto, Haskell evalúa algo solamente cuando lo necesita. En consecuencia, provee una forma muy elegante de manipular estructuras infinitas, por ejemplo.
Una ultima advertencia sobre como deberías leer código Haskell. Para mi, es
como leer artículos científicos. Algunas partes son muy claras, pero cuando vez
una formula, enfócate y lee más despacio. También, mientras se lee código
Haskell, en realidad no importa mucho si no se comprenden los detalles
de la sintaxis. Si encuentras algo como >>=
, <$>
, <-
o cualquier
símbolo extraño, solamente ignóralos y continua el flujo del código.
Seguramente estarás acostumbrado a funciones como:
En C
:
int f(int x, int y) {
return x*x + y*y;
}
En JavaScript
:
function f(x,y) {
return x*x + y*y;
}
En Python:
def f(x,y):
return x*x + y*y
En Ruby:
def f(x,y)
*x + y*y
xend
En Scheme:
define (f x y)
(+ (* x x) (* y y))) (
Finalmente, en Haskell es:
= x*x + y*y f x y
Muy limpio. No paréntesis, no def.
No olvides, Haskell usa funciones y tipos un montón. Por lo que es muy fácil definirlos. La sintaxis fuer particularmente pensada para estos elementos.
Aunque no es obligatorio, la información sobre los tipos para las funciones usualmente se hace explicita. No es obligatorio por que el compilador es lo bastante inteligente para descubrirlo por ti. Es una buena idea hacerlo de todas formas por que indica la intensión y facilita la comprensión.
Juguemos un poco. Declaramos el tipo usando ::
f :: Int -> Int -> Int
= x*x + y*y
f x y
= print (f 2 3) main
$ runhaskell 20_very_basic.lhs
13
Ahora intenta
f :: Int -> Int -> Int
= x*x + y*y
f x y
= print (f 2.3 4.2) main
Deberías obtener este error:
21_very_basic.lhs:6:23:
No instance for (Fractional Int)
arising from the literal `4.2'
Possible fix: add an instance declaration for (Fractional Int)
In the second argument of `f', namely `4.2'
In the first argument of `print', namely `(f 2.3 4.2)'
In the expression: print (f 2.3 4.2)
El problema: 4.2 no es un Int
.
La solución: No declarar un tipo para f
por el momento y dejar a Haskell
inferir el tipo más general por nosotros:
= x*x + y*y
f x y
= print (f 2.3 4.2) main
Funciona! Afortunadamente, no tenemos que declarar una nueva función para cada
tipo. Por ejemplo, in C
, deberíamos declarar una función para int
,
para float
para long
, para double
, etc…
Pero, que tipo deberíamos declarar? Para descubrir el tipo que Haskell a usado por nosotros ejecutaremos ghci:
% ghci
GHCi, version 7.0.4: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading package ffi-1.0 ... linking ... done.
Prelude>
Y escribimos:
let f x y = x*x + y*y
Prelude>
:type f
f :: Num a => a -> a -> a
Uh? Que es ese tipo extraño?
Num a => a -> a -> a
Primero, enfoquémonos en la parte de la derecha a -> a -> a
. Para
comprenderlo, solo mira una lista de ejemplos progresivos:
El tipo | Su significado |
---|---|
Int | El tipo Int |
Int -> Int | El tipo de función de Int a Int |
Float -> Int | El tipo de función de Float a Int |
a -> Int | El tipo de función de cualquier tipo a Int |
a -> a | El tipo de función de cualquier tipo al mismo tipo a |
a -> a -> a | El tipo de función de dos argumentos de cualquier tipo a al |
mismo tipo a |
En el tipo a -> a -> a
, la letra a
es una variable de tipo. Significa que
f
es una función con dos argumentos y esos dos argumentos y el resultado
tienen que ser del mismo tipo, La variable de tipo a
puede ser cualquier tipo.
Por ejemplo Int
, Integer
, Float
…
Así que en lugar de forzar un tipo en particular como en C
y tener que
declarar una función para int
, long
, float
, double
, etc., podemos
declarar una sola función como en un lenguaje de tipado dinámico.
Esto es algunas veces llamado polimorfismo paramétrico.
Generalmente a
puede ser cualquier tipo, por ejemplo un String
a un Int
,
pero también puede ser tipos más complejos, como Trees
, otras funciones,
etc. Pero en este caso nuestro tipo tiene como prefijo Num a =>
.
Num
es una clase de tipo (type class). Una clase de tipo puede ser vista
como un conjunto de tipos. Num
contiene solamente los tipos que pueden
comportarse como números. Más concretamente, Num
es una clase que contiene
tipos que implementan una lista especifica de funciones, en particular
(+)
y (*)
.
Las clases de tipos son un aspecto muy potente del lenguaje. Podemos hacer cosas increíbles con esto. Más sobre el tema luego.
Finalmente, Num a => a -> a -> a
significa:
Sea a
un tipo que pertenece a la clase de tipo Num
. Esto es una función de
tipo a
a (a -> a
).
Si, extraño. De hecho, en Haskell ninguna función tiene dos argumentos. En lugar de eso todas las funciones pueden tener un solo argumento. Pero notaremos que tomar dos argumentos es equivalente a tomar un argumento y retornar una función que toma el segundo argumento como parámetro.
Más concretamente f 3 4
es equivalente a (f 3) 4
. Nótese que f 3
es una
función:
f :: Num a => a -> a -> a
g :: Num a => a -> a
= f 3
g
3*3 + y*y g y ⇔
Existe otra notación para funciones. La notación lambda nos permite crear funciones sin asignarles un nombre. Llamamos a estas funciones anónimas. Podemos escribirlas como:
= \y -> 3*3 + y*y g
El \\
es usado por que se parece a λ
(símbolo lambda) y es ASCII.
Si no estás acostumbrado a la programación funcional tu cerebro debería estar empezando a calentarse. Es tiempo de hacer una aplicación real.
Pero antes de eso, deberíamos verificar que el sistema de tipos funciona según lo esperado.
f :: Num a => a -> a -> a
= x*x + y*y
f x y
= print (f 3 2.4) main
Funciona, porque, 3
es una representación valida para números
fraccionarios como Float
así como para Integer
. Como 2.4
es una numero
fraccionario, 3
es interpretado también como un numero fraccionario.
Si forzamos nuestra función a trabajar con tipos diferentes, fallará.
f :: Num a => a -> a -> a
= x*x + y*y
f x y
x :: Int
= 3
x y :: Float
= 2.4
y -- No funcionará por que el tipo x ≠ tipo y
= print (f x y) main
El compilador se queja. Los dos parámetros deben ser del mismo tipo.
Si piensas que esto es una mala idea, y que el compilador debería hacer la transformación de un tipo al otro por ti, deberías ver este fantástico (y divertido) vídeo: WAT
Sugiero que leas con ligereza esta parte. Mírala como una referencia. Haskell tiene un montón de características. Regresa aquí cada vez que la notación te parezca extraña.
Uso el símbolo ⇔
para indicar que dos expresiones son equivalentes. Es una
meta notación, ⇔
no existe en Haskell. También usaré ⇒
para indicar
cual es el valor de retorno de una expresión.
Aritmética
3 + 2 * 6 / 3 ⇔ 3 + ((2*6)/3)
Lógica
True || False ⇒ True
True && False ⇒ False
True == False ⇒ False
True /= False ⇒ True (/=) es el operador diferencia
Potencias
x^n para un n entero (Int o Integer)
x**y para cualquier tipo de numero y (como un Float)
Integer
no tiene ningún limite además de la capacidad de tu máquina.
4^103
102844034832575377634685573909834406561420991602098741459288064
Si! También hay números racionales! Pero hay que importar el modulo
Data.Ratio
:
$ ghci
....
Prelude> :m Data.Ratio
Data.Ratio> (11 % 15) * (5 % 3)
11 % 9
Listas
[] ⇔ Lista vacia
[1,2,3] ⇔ Lista de enteros
["foo","bar","baz"] ⇔ Lista de cadenas
1:[2,3] ⇔ [1,2,3], (:) anteponer un elemento
1:2:[] ⇔ [1,2]
[1,2] ++ [3,4] ⇔ [1,2,3,4], (++) concatenar
[1,2,3] ++ ["foo"] ⇔ ERROR String ≠ Integral
[1..4] ⇔ [1,2,3,4]
[1,3..10] ⇔ [1,3,5,7,9]
[2,3,5,7,11..100] ⇔ ERROR! No soy tan inteligente!
[10,9..1] ⇔ [10,9,8,7,6,5,4,3,2,1]
Cadenas
En Haskell las cadenas son listas de Char
.
'a' :: Char
"a" :: [Char]
"" ⇔ []
"ab" ⇔ ['a','b'] ⇔ 'a':"b" ⇔ 'a':['b'] ⇔ 'a':'b':[]
"abc" ⇔ "ab"++"c"
En código real no se debería usar una lista de `Char` para
representar texto. Se debería usar `Data.Text`. Si quieres
representar un flujo de caracteres ASCII, deberías usar
`Data.ByteString`.
Tuplas
El tipo de una tupla es (a,b)
. Los elementos dentro de una tupla pueden
tener diferentes tipos.
-- Todas estas tuplas son validas
(2,"foo")
(3,'a',[2,3])
((2,"a"),"c",3)
fst (x,y) ⇒ x
snd (x,y) ⇒ y
fst (x,y,z) ⇒ ERROR: fst :: (a,b) -> a
snd (x,y,z) ⇒ ERROR: snd :: (a,b) -> b
Controlar los paréntesis
Para remover algunos paréntesis se pueden usar dos funciones: ($)
y (.)
.
-- Por defecto:
f g h x ⇔ (((f g) h) x)
-- el $ reemplaza los paréntessis desde el $
-- hasta el final de la expresión
f g $ h x ⇔ f g (h x) ⇔ (f g) (h x)
f $ g h x ⇔ f (g h x) ⇔ f ((g h) x)
f $ g $ h x ⇔ f (g (h x))
-- (.) composición de funciones
(f . g) x ⇔ f (g x)
(f . g . h) x ⇔ f (g (h x))
Solo un recordatorio:
x :: Int ⇔ x es de tipo Int
x :: a ⇔ x puede ser de cualquier tipo
x :: Num a => a ⇔ x puede ser cualquier tipo a
que pertenezca a la class de typo Num
f :: a -> b ⇔ f es una función de a hacia b
f :: a -> b -> c ⇔ f es una función de a hacia (b→c)
f :: (a -> b) -> c ⇔ f es una función de (a→b) hacia c
Recuerda que definir el tipo de una función antes de su declaración no es obligatorio. Haskell infiere el tipo más general por ti. Pero es considerado una buena practica hacerlo de todos modos.
Notación infijo
square :: Num a => a -> a
= x^2 square x
Nótese que ^
usa notación infijo. Para cada operador infijo hay una notación
prefijo asociada. Solo debe ponerse entre paréntesis.
= (^) x 2
square' x = (^2) x square'' x
Podemos remover x
en el lado izquierdo y derecho!
Eso se llama reducción η.
= (^2) square'''
Nótese que podemos declarar funciones con un '
en su nombre:
square ⇔ square' ⇔ square'' ⇔ square'''
Tests
absolute :: (Ord a, Num a) => a -> a
= if x >= 0 then x else -x absolute x
Nota: el if .. then .. else
en Haskell es como el algo ? algo : algo
en C.
No puedes olvidar el else
Otra versión equivalente:
absolute' x| x >= 0 = x
| otherwise = -x
Advertencia: la indentación es importante en Haskell. Como en
Python, una mala indentación pueden dañar el código!
La parte difícil puede empezar ahora.
En esta sección, proporcionaré un ejemplo corto de la impresionante habilidad para refactorizar de Haskell. Seleccionaremos un problema y los resolveremos en la forma imperativa estándar. Luego desarrollaremos el código. Al final el resultado será más elegante y más sencillo de adaptar.
Solucionemos el siguiente problema:
Dada una lista de enteros, retornar la suma de numeros pares en la lita.
ejemplo: `[1,2,3,4,5] ⇒ 2 + 4 ⇒ 6`
Para mostrar las diferencias entre los enfoques funcional e imperativo, Empezaré con la solución imperativa (en JavaScript):
function evenSum(list) {
var result = 0;
for (var i=0; i< list.length ; i++) {
if (list[i] % 2 ==0) {
+= list[i];
result
}
}return result;
}
En Haskell, en contraste, no tenemos variables ni un loop for
. Una solución
para lograr el mismo resultado sin loops es usando recursión.
Nota: La recursión es generalmente persivida como lenta en los lengajes
imperativos. Pero generalmente no es el caso en la programación funcional.
La mayor parte del tiempo Haskell manejará funciones recursivas de forma
eficiente.
Aquí esta la versión en C
de la función recursiva. Por simplicidad asumí que
la lista de int
termina con el primer valor de 0
.
int evenSum(int *list) {
return accumSum(0,list);
}
int accumSum(int n, int *list) {
int x;
int *xs;
if (*list == 0) { // si la lista está vacia
return n;
} else {
= list[0]; // x es el primer elemento de la lista
x = list+1; // xs es la lista sin el elemento x
xs if ( 0 == (x%2) ) { // si x es par
return accumSum(n+x, xs);
} else {
return accumSum(n, xs);
}
}
}
Mantén este código en mente. Lo vamos a traducir a Haskell. Sin embargo, vamos a necesitar primero introducir tres simples pero útiles funciones que usaremos:
even :: Integrall a => a -> Bool
head :: [a] -> a
tail :: [a] -> [a]
even
verifica si un numero es par.
even :: Integral a => a -> Bool
even 3 ⇒ False
even 2 ⇒ True
head
retorna el primer elemento de la lista:
head :: [a] -> a
head [1,2,3] ⇒ 1
head [] ⇒ ERROR
tail
retorna todos los elementos de la lista, excepto el primero:
tail :: [a] -> [a]
tail [1,2,3] ⇒ [2,3]
tail [3] ⇒ []
tail [] ⇒ ERROR
Nótese que para cualquier lista no vacía l
, l ⇔ (head l):(tail l)
La primera solución en Haskell. La función evenSum
retorna la suma de todos
los números pares en la lista:
-- Version 1
evenSum :: [Integer] -> Integer
= accumSum 0 l
evenSum l
= if l == []
accumSum n l then n
else let x = head l
= tail l
xs in if even x
then accumSum (n+x) xs
else accumSum n xs
Para probar la función puedes usar ghci
:
% ghci
GHCi, version 7.0.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :load 11_Functions.lhs
[1 of 1] Compiling Main ( 11_Functions.lhs, interpreted )
Ok, modules loaded: Main.
*Main> evenSum [1..5]
6
Aquí un ejemplo de la ejecución[^2]:
*Main> evenSum [1..5]
accumSum 0 [1,2,3,4,5]
1 is odd
accumSum 0 [2,3,4,5]
2 is even
accumSum (0+2) [3,4,5]
3 is odd
accumSum (0+2) [4,5]
2 is even
accumSum (0+2+4) [5]
5 is odd
accumSum (0+2+4) []
l == []
0+2+4
0+6
6
Viniendo de un lenguaje imperativo todo debería parecer correcto. De hecho, muchas cosas se pueden mejorar. Primero, podemos generalizar el tipo.
evenSum :: Integral a => [a] -> a
Luego, podemos usar sub-funciones usando where
o let
. De esta forma la
función accumSum
no llenará el espacio de nombres de nuestro modulo.
-- Version 2
evenSum :: Integral a => [a] -> a
= accumSum 0 l
evenSum l where accumSum n l =
if l == []
then n
else let x = head l
= tail l
xs in if even x
then accumSum (n+x) xs
else accumSum n xs
Luego podemos usar pattern matching.
-- Version 3
= accumSum 0 l
evenSum l where
= n
accumSum n [] :xs) =
accumSum n (xif even x
then accumSum (n+x) xs
else accumSum n xs
Qué es pattern matching? Usar valores en lugar de nombres de parámetro generales[^3].
En lugar de decir: foo l = if l == [] then <x> else <y>
simplemente se
declara:
= <x>
foo [] = <y> foo l
Pero el pattern matching va más lejos. También es capaz de inspeccionar el elemento interno de un valor complejo. Podemos reemplazar
= let x = head l
foo l = tail l
xs in if even x
then foo (n+x) xs
else foo n xs
Con
:xs) = if even x
foo (xthen foo (n+x) xs
else foo n xs
Esto es una característica muy útil. Hace nuestro código más conciso y fácil de leer.
En Haskell se puede simplificar las definiciones de las funciones usando reducción η. Por ejemplo, en lugar de escribir:
= (una expresion) x f x
Se puede escribir
= una expression f
Usamos este método para remover el l
:
-- Version 4
evenSum :: Integral a => [a] -> a
= accumSum 0
evenSum where
= n
accumSum n [] :xs) =
accumSum n (xif even x
then accumSum (n+x) xs
else accumSum n xs
Para mejorarlo aún más podemos usar funciones de orden superior. Qué son esas bestias? Las funciones de orden superior son funciones que toman funciones como parámetros.
Aquí algunos ejemplos:
filter :: (a -> Bool) -> [a] -> [a]
map :: (a -> b) -> [a] -> [b]
foldl :: (a -> b -> a) -> a -> [b] -> a
Procedamos con pequeños pasos.
-- Version 5
= mysum 0 (filter even l)
evenSum l where
= n
mysum n [] :xs) = mysum (n+x) xs mysum n (x
Donde
filter even [1..10] ⇔ [2,4,6,8,10]
La función filter
toma una función de tipo (a -> Bool
) y una lista de
tipo [a]
. Retorna una lista que contiene solamente los elementos para los
cuales la función retornó true
.
El siguiente paso es usar otra técnica para lograr el mismo resultado que un
loop. Usaremos la función foldl
para acumular los valores mientras recorremos
la lista. La función foldl
captura un patrón común:
= foo initialValue list
myfunc list = accumulated
foo accumulated [] :xs) = foo (bar tmpValue x) xs foo tmpValue (x
Que se puede reemplazar con:
= foldl bar initialValue list myfunc list
Si realmente quieres saber como funciona la magia, aquí está la definición
de foldl
:
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
foldl f z [x1,...xn]
... (f (f z x1) x2) ...) xn ⇔ f (
Pero como Haskell es perezoso, no evalúa (f z x)
y simplemente lo empuja en
la pila. Por eso generalmente usamos foldl'
en lugar de foldl
; foldl'
es una versión estricta de foldl
. Si no comprendes que significa perezoso
o estricto, no te preocupes, solo sigue el código como si fold
y foldl'
fueran idénticos.
Ahora la nueva versión de evenSum
será:
-- Version 6
-- foldl' isn't accessible by default
-- we need to import it from the module Data.List
import Data.List
= foldl' mysum 0 (filter even l)
evenSum l where mysum acc value = acc + value
También podemos simplificar eso usando notación lambda. Así no tendremos
que crear un nombre temporal mysum
.
-- Version 7
-- Generally it is considered a good practice
-- to import only the necessary function(s)
import Data.List (foldl')
= foldl' (\x y -> x+y) 0 (filter even l) evenSum l
Y por supuesto, notamos que
(\x y -> x+y) ⇔ (+)
Finalmente
-- Version 8
import Data.List (foldl')
evenSum :: Integral a => [a] -> a
= foldl' (+) 0 (filter even l) evenSum l
foldl'
no es la función más sencilla de comprender. Si no estas
acostumbrado, deberías estudiarlo un poco.
Para ayudar a comprender que está sucediendo aquí, miremos la evaluación paso por paso:
evenSum [1,2,3,4]
⇒ foldl' (+) 0 (filter even [1,2,3,4])
⇒ foldl' (+) 0 [2,4]
⇒ foldl' (+) (0+2) [4]
⇒ foldl' (+) 2 [4]
⇒ foldl' (+) (2+4) []
⇒ foldl' (+) 6 []
⇒ 6
Otra función de orden superior útil es (.)
. La función (.)
corresponde
a la composición matemática de funciones.
(f . g . h) x ⇔ f ( g (h x))
Podemos tomar ventaja de este operador para hacer una reducción η en nuestra función:
-- Version 9
import Data.List (foldl')
evenSum :: Integral a => [a] -> a
= (foldl' (+) 0) . (filter even) evenSum
También, podemos renombrar algunas partes para hacerlo más claro:
-- Version 10
import Data.List (foldl')
sum' :: (Num a) => [a] -> a
= foldl' (+) 0
sum' evenSum :: Integral a => [a] -> a
= sum' . (filter even) evenSum
Es tiempo de hablar sobre la dirección hacia la cual se ha movido nuestro código mientras introducimos más de la forma funcional. Qué hemos ganado al usar funciones de orden superior?
Al principio podrías pensar que la principal diferencia es la brevedad. Pero en realidad tiene más que ver con la forma en la que se piensa. Supongamos que se quiere modificar un poco la función, por ejemplo, para obtener la suma de los cuadrados de los elementos pares de la lista.
1,2,3,4] ▷ [1,4,9,16] ▷ [4,16] ▷ 20 [
Actualizar la versión 10 es muy fácil:
= sum' . (filter even) . (map (^2))
squareEvenSum = evenSum . (map (^2)) squareEvenSum'
Solamente agregamos otra “función de transformación”.
map (^2) [1,2,3,4] ⇔ [1,4,9,16]
La función map
simplemente aplica una función sobre todos los elementos
de una lista.
No tuvimos que modificar nada dentro de la definición de la función. Esto hace el código más modular. Pero también permite pensar en la función de forma más matemática. También se puede usar la función intercambiablemente con otras, según se necesite. Esto es, se puede hacer compose, map, fold, filter usando la nueva función.
Modificar la versión 1 se deja como ejercicio para el lector ☺.
Si piensas que hemos llegado al final de la generalización, entonces enterate que estas muy equivocado. Por ejemplo, hay una forma de usar esta fucnion no solo en listas, sino ademas sobre cualquier tipo recursivo. Si quieres saber como, recomiendo que leas este artículo
Este ejemplo debería demostrar cuan genial es la programación funcional. Desafortunadamente, usar programación funcional pura no es adecuado para todos los usos. O al menos un lenguaje que lo permite no se a logrado aún.
Uno de los mayores poderes de Haskell es la habilidad de crear DSLs (Lenguaje de dominio específico) haciendo sencillo cambiar el paradigma de programación.
De hecho, Haskell también es grandioso cuando se quiere escribir en estilo imperativo. Comprender esto fue muy difícil para mi cuando aprendía Haskell. Gran parte del esfuerzo se va intentando explicar la superioridad del enfoque funcional. Luego cuando empiezas a usar estilo imperativo con Haskell, puede ser difícil entender dónde y cómo usarlo.
Pero antes de hablar sobre este super poder de Haskell, debemos hablar sobre otro aspecto esencial de Haskell: Tipos.
TL;DR*:
type Nombre = OtroTipo
es solamente un alias y no hay ninguna diferencia
entre Nombre
y OtroTipo
.
data Nombre = NombreConstructor OtroTipo
tiene diferencia
data
puede construir estructuras que pueden ser recursivas
deriving
es mágico y crea funciones por ti
En Haskell, los tipos son fuertes y estáticos.
Por qué es importante? Permitirá en gran medida evitar errores. En Haskell, la mayoría de los errores se capturan durante la compilación del programa. Y la razón principal es debido a la inferencia de tipos durante la compilación. La inferencia de tipos hace sencillo detectar donde se usó el parámetro incorrecto en el lugar incorrecto, por ejemplo.
El tipado estático es generalmente esencial para la ejecución veloz. Pero la mayoría de lenguajes estáticamente tipado son malos generalizando conceptos. La ventaja de Haskell es su capacidad para inferir tipos.
Aquí un ejemplo simple, la función square
en Haskell:
= x * x square x
Esta función puede elevar al cuadrado cualquier tipo Numérico. Se puede pasar
a square
un Int
, un Integer
, un Float
, un Fractional
e incluso un
Complex
. Por ejemplo:
% ghci
GHCi, version 7.0.4:
...
Prelude> let square x = x*x
Prelude> square 2
4
Prelude> square 2.1
4.41
Prelude> -- load the Data.Complex module
Prelude> :m Data.Complex
Prelude Data.Complex> square (2 :+ 1)
3.0 :+ 4.0
x :+ y
es la notación para el complejo (x + iy).
Ahora compáralo con la cantidad de código necesario en C
:
int int_square(int x) { return x*x; }
float float_square(float x) {return x*x; }
complex complex_square (complex z) {
complex tmp;
.real = z.real * z.real - z.img * z.img;
tmp.img = 2 * z.img * z.real;
tmp}
complex x,y;
= complex_square(x); y
Para cada tipo, se necesita escribir una nueva función. La única forma de solucionar esto es usando algún truco de meta-programación, por ejemplo usando el pre-procesador. En C++ hay una mejor forma usando templates:
#include <iostream>
#include <complex>
using namespace std;
template<typename T>
(T x)
T square{
return x*x;
}
int main() {
// int
int sqr_of_five = square(5);
<< sqr_of_five << endl;
cout // double
<< (double)square(5.3) << endl;
cout // complex
<< square( complex<double>(5,3) )
cout << endl;
return 0;
}
C++ lo hace mucho mejor que C en este aspecto. Pero para funciones más complejas la sintaxis puede ser difícil de entender: Mira este artículo por ejemplo.
En C++ se debe declarar que la función puede trabajar con distintos tipos. En Haskell, es lo opuesto. La función será lo más general posible por defecto.
La inferencia de tipos le da a Haskell la sensación de libertad de los lenguajes de tipado dinámico. Pero a diferencia de estos, la mayoría de los errores se encuentra antes de la ejecución. Generalmente, en Haskell:
“Si compila entonces hace lo que quieres que haga”
Es posible construir tipos propios. Primero, se pueden usar alias o sinónimos de tipos.
type Name = String
type Color = String
showInfos :: Name -> Color -> String
= "Name: " ++ name
showInfos name color ++ ", Color: " ++ color
name :: Name
= "Robin"
name color :: Color
= "Blue"
color = putStrLn $ showInfos name color main
Pero esto no te protege mucho. Intenta intercambiar los dos parámetros de
showInfos
y ejecuta el programa:
putStrLn $ showInfos color name
Se compilará y ejecutará. De hecho se pueden reemplazar Name
, Color
y
String
con cualquier cosa. El compilador los tratará como si fueran
idénticos.
Otro método es crear tus propios tipos usando la palabra reservada data
.
data Name = NameConstr String
data Color = ColorConstr String
showInfos :: Name -> Color -> String
NameConstr name) (ColorConstr color) =
showInfos ("Name: " ++ name ++ ", Color: " ++ color
= NameConstr "Robin"
name = ColorConstr "Blue"
color = putStrLn $ showInfos name color main
Ahora si intercambias los parámetros de showInfos
, el compilador se queja!
De forma que nunca más podrás cometer un error de ese tipo y el único
precio es ser un poco más explicito.
También nota que los constructores son funciones:
NameConstr :: String -> Name
ColorConstr :: String -> Color
La sintaxis de data
es principalmente:
data TypeName = ConstructorName [types]
| ConstructorName2 [types]
| ...
Generalmente se usa el mismo nombre para el DataTypeName
y para el
DataTypeConstructor
.
Ejemplo:
data Complex a = Num a => Complex a a
También se puede usar sintaxis record:
data DataTypeName = DataConstructor {
field1 :: [type of field1]
field2 :: [type of field2]
,...
fieldn :: [type of fieldn] } ,
Y hay varios accesores disponibles. Además se puede usar otro orden cuando se asignen valores.
Ejemplo:
data Complex a = Num a => Complex { real :: a, img :: a}
= Complex 1.0 2.0
c = Complex { real = 3, img = 4 }
z 1.0
real c ⇒ 4 img z ⇒
Ya nos hemos topado con un tipo recursivo: listas. Se pueden re-crear listas, pero con una sintaxis más explicita:
data List a = Empty | Cons a (List a)
Si prefieres usar una sintaxis más simple, se puede usar un nombre infijo para los constructores.
infixr 5 :::
data List a = Nil | a ::: (List a)
El numero luego de infixr
le da la precedencia.
Si quieres poder imprimir por pantalla (Show
), leer (Read
), probar
igualdad (Eq
) y comparar (Ord
) con tu nueva estructura de datos puedes
pedirle a Haskell que derive las funciones apropiadas por ti.
infixr 5 :::
data List a = Nil | a ::: (List a)
deriving (Show,Read,Eq,Ord)
Cuando añades deriving (Show)
a tu declaración de datos, Haskell crea una
función show
por ti. Ya veremos como se puede usar una función show
propia.
= Nil
convertList [] :xs) = x ::: convertList xs convertList (x
= do
main print (0 ::: 1 ::: Nil)
print (convertList [0,1])
Esto imprime:
0 ::: (1 ::: Nil)
0 ::: (1 ::: Nil)
Otro ejemplo estándar: arboles binarios.
import Data.List
data BinTree a = Empty
| Node a (BinTree a) (BinTree a)
deriving (Show)
También crearemos una función que convierta una lista en un árbol binario ordenado.
treeFromList :: (Ord a) => [a] -> BinTree a
= Empty
treeFromList [] :xs) = Node x (treeFromList (filter (<x) xs))
treeFromList (xfilter (>x) xs)) (treeFromList (
Observa cuan elegante es esta función.
(x:xs)
será convertida en un árbol donde:
** La raíz es x
** El sub-árbol de la izquierda es el árbol creado de los miembros de la lista
** xs
que son menores a x
y
** El sub-árbol de la derecha es el árbol creado de los miembros de la lista xs
** que son mayores que x
.= print $ treeFromList [7,2,4,8] main
Deberías obtener lo siguiente:
Node 7 (Node 2 Empty (Node 4 Empty Empty)) (Node 8 Empty Empty)
Esta es una forma informativa pero no muy agradable de nuestro árbol.
Solo por diversión, hagamos que nuestros arboles se visualicen de una mejor forma. Simplemente resulta divertido hacer una función para mostrar arboles en una forma general. Puedes saltarte esta parta si te parece muy difícil.
Tenemos unos cuantos cambios que hacer. Remover el deriving (Show)
de la
declaración del tipo BinTree
. Y también sería útil hacer nuestras propias
infancias de (Eq
y Ord
) de forma que podamos probar igualdad y comprar
arboles.
data BinTree a = Empty
| Node a (BinTree a) (BinTree a)
deriving (Eq,Ord)
Sin el deriving (Show)
, Haskell no creará una función show
por nosotros.
Crearemos nuestra propia versión de show
. Para lograrlo, debemos declarar
que nuestro nuevo tipo BinTree a
es una instancia de la clase de tipo
Show
. La sintaxis general es:
instance Show (BinTree a) where
show t = ... -- You declare your function here
Aquí está mi versión de como mostrar un árbol binario. No te preocupes de la aparente complejidad. Hice un montón de mejoras para mostrar incluso objetos extraños.
-- declare BinTree a to be an instance of Show
instance (Show a) => Show (BinTree a) where
-- will start by a '<' before the root
-- and put a : a begining of line
show t = "< " ++ replace '\n' "\n: " (treeshow "" t)
where
-- treeshow pref Tree
-- shows a tree and starts each line with pref
-- We don't display the Empty tree
Empty = ""
treeshow pref -- Leaf
Node x Empty Empty) =
treeshow pref (
(pshow pref x)
-- Right branch is empty
Node x left Empty) =
treeshow pref (++ "\n" ++
(pshow pref x) "`--" " " left)
(showSon pref
-- Left branch is empty
Node x Empty right) =
treeshow pref (++ "\n" ++
(pshow pref x) "`--" " " right)
(showSon pref
-- Tree with left and right children non empty
Node x left right) =
treeshow pref (++ "\n" ++
(pshow pref x) "|--" "| " left) ++ "\n" ++
(showSon pref "`--" " " right)
(showSon pref
-- shows a tree using some prefixes to make it nice
=
showSon pref before next t ++ before ++ treeshow (pref ++ next) t
pref
-- pshow replaces "\n" by "\n"++pref
= replace '\n' ("\n"++pref) (show x)
pshow pref x
-- replaces one char by another string
=
replace c new string concatMap (change c new) string
where
change c new x| x == c = new
| otherwise = x:[] -- "x"
El método treeFromList
permanece idéntico.
treeFromList :: (Ord a) => [a] -> BinTree a
= Empty
treeFromList [] :xs) = Node x (treeFromList (filter (<x) xs))
treeFromList (xfilter (>x) xs)) (treeFromList (
Y ahora, podemos jugar:
= do
main putStrLn "Int binary tree:"
print $ treeFromList [7,2,4,8,1,3,6,21,12,23]
Int binary tree:
< 7
: |--2
: | |--1
: | `--4
: | |--3
: | `--6
: `--8
: `--21
: |--12
: `--23
Ahora es mucho mejor! La raíz se muestra iniciando la linea con <
. Y cada
linea que le sigue inicia con :
. Pero también podríamos usar otro tipo.
putStrLn "\nString binary tree:"
print $ treeFromList ["foo","bar","baz","gor","yog"]
String binary tree:
< "foo"
: |--"bar"
: | `--"baz"
: `--"gor"
: `--"yog"
Como podemos probar igualdad y ordenar arboles, podemos hacer arboles de arboles!
putStrLn "\nBinary tree of Char binary trees:"
print ( treeFromList
map treeFromList ["baz","zara","bar"])) (
Binary tree of Char binary trees:
< < 'b'
: : |--'a'
: : `--'z'
: |--< 'b'
: | : |--'a'
: | : `--'r'
: `--< 'z'
: : `--'a'
: : `--'r'
Por eso elegí poner un :
en cada linea del árbol (excepto en la raíz).
putStrLn "\nTree of Binary trees of Char binary trees:"
print $ (treeFromList . map (treeFromList . map treeFromList))
"YO","DAWG"]
[ ["I","HEARD"]
, ["I","HEARD"]
, ["YOU","LIKE","TREES"] ] , [
Que es equivalente a
print ( treeFromList (
map treeFromList
map treeFromList ["YO","DAWG"]
[ map treeFromList ["I","HEARD"]
, map treeFromList ["I","HEARD"]
, map treeFromList ["YOU","LIKE","TREES"] ])) ,
Y produce:
Binary tree of Binary trees of Char binary trees:
< < < 'Y'
: : : `--'O'
: : `--< 'D'
: : : |--'A'
: : : `--'W'
: : : `--'G'
: |--< < 'I'
: | : `--< 'H'
: | : : |--'E'
: | : : | `--'A'
: | : : | `--'D'
: | : : `--'R'
: `--< < 'Y'
: : : `--'O'
: : : `--'U'
: : `--< 'L'
: : : `--'I'
: : : |--'E'
: : : `--'K'
: : `--< 'T'
: : : `--'R'
: : : |--'E'
: : : `--'S'
Nota como arboles duplicados no son insertados; solo hay un árbol
correspondiente a "I", "HEARD"
. Podemos tener esto (casi)
gratuitamente, por que hemos declarado que el tipo árbol es una instancia
de Eq
.
Mira cuan genial es esta estructura: Podemos hacer arboles que contienen no solo enteros, cadenas y caracteres, sino también arboles. Y podemos incluso hacer un árbol que contenga arboles de arboles!
Es común escuchar que Haskell es perezoso.
De hecho, si se es un poco pedante, se debería decir que Haskell es no-estricto.. Pereza es solo una implementación común de lenguajes no-estrictos.
¿Pero qué significa “no-estricto”? Desde la wiki de Haskell:
Reduction (the mathematical term for evaluation) proceeds from the outside in.
so if you have (a+(b*c)) then you first reduce + first, then you reduce the inner (b*c)
Por ejemplo en Haskell se puede hacer:
-- numbers = [1,2,..]
numbers :: [Integer]
= 0:map (1+) numbers
numbers
= []
take' n [] 0 l = []
take' :xs) = x:take' (n-1) xs
take' n (x
= print $ take' 10 numbers main
Y se detiene.
¿Cómo?
En lugar de intentar evaluar numbers
por completo, evalúa los elementos solo
cunado se los necesita.
También, nótese que en Haskell hay una notación para listas infinitas.
1..] ⇔ [1,2,3,4...]
[1,3..] ⇔ [1,3,5,7,9,11...] [
Y la mayoría de las funciones funcionarán con ellas. También, hay una función
take
que es equivalente a nuestro take'
Supón que queremos un árbol ordenado binario. Aquí hay una árbol binario infinito.
= Node 0 nullTree nullTree nullTree
Un árbol binario completo donde cada nodo es iguala a 0. Ahora probaré que se puede manipular este objeto usando la siguiente función:
-- take all element of a BinTree
-- up to some depth
Empty = Empty
treeTakeDepth _ 0 _ = Empty
treeTakeDepth Node x left right) = let
treeTakeDepth n (= treeTakeDepth (n-1) left
nl = treeTakeDepth (n-1) right
nr in
Node x nl nr
Mira lo que ocurre con este programa:
= print $ treeTakeDepth 4 nullTree main
Este código compila, se ejecuta y se detiene dando el siguiente resultado:
< 0
: |-- 0
: | |-- 0
: | | |-- 0
: | | `-- 0
: | `-- 0
: | |-- 0
: | `-- 0
: `-- 0
: |-- 0
: | |-- 0
: | `-- 0
: `-- 0
: |-- 0
: `-- 0
Solo para calentar tus neuronas, hagamos un árbol más interesante:
= Node 0 (dec iTree) (inc iTree)
iTree where
Node x l r) = Node (x-1) (dec l) (dec r)
dec (Node x l r) = Node (x+1) (inc l) (inc r) inc (
Otra forma de crear ese árbol es usar una función de orden superior. Esta
función debería ser similar a map
, pero debería funcionar en BinTree
en
lugar de una lista.
Aquí está la función:
-- apply a function to each node of Tree
treeMap :: (a -> b) -> BinTree a -> BinTree b
Empty = Empty
treeMap f Node x left right) = Node (f x)
treeMap f (
(treeMap f left) (treeMap f right)
Nota: No hablaré más de esto aquí. Si estas interesado en la generalización de
map
a otras estructuras de datos, busca functor y fmap
.
Nuestra definición es ahora:
infTreeTwo :: BinTree Int
= Node 0 (treeMap (\x -> x-1) infTreeTwo)
infTreeTwo -> x+1) infTreeTwo) (treeMap (\x
Observa el resultado de
= print $ treeTakeDepth 4 infTreeTwo main
< 0
: |-- -1
: | |-- -2
: | | |-- -3
: | | `-- -1
: | `-- 0
: | |-- -1
: | `-- 1
: `-- 1
: |-- 0
: | |-- -1
: | `-- 1
: `-- 2
: |-- 1
: `-- 3
Felicitaciones por llegar tan lejos! Ahora, algunas de las cosas realmente difíciles pueden empezar.
Si eres como yo, ya deberías comprender el estilo funcional. También deberías entender un poco más de las ventajas de la pereza (Laziness) por defecto. Pero también deberías NO comprender aún como empezar a escribir un programa real. Y en particular:
Prepárate, las respuestas pueden ser complejas. Pero son realmente gratificantes.
TL;DR:
Una función típica haciendo IO es muy parecida a un programa imperativo:
f :: IO a
= do
f <- action1
x
action2 x<- action3
y action4 x y
<-
.IO *
; en este ejemplo:action1 :: IO b
action2 x :: IO ()
action3 :: IO c
action4 x y :: IO a
x :: b
, y :: c
IO a
, esto debería ayudar a elegir. En
particular no se deberían usar funciones puras aquí. Para usar funciones
se puede hacer action2 (purefunction x)
por ejemplo.En esta sección,, explicaré como usar IO, no como funciona. Se verá como Haskell separa la partes puras del programa de las impuras.
No te detengas por que intentas comprender los detalles de la sintaxis. Las respuestas vendrán en la siguiente sección.
Qué queremos lograr?
Pedir al usuario que ingrese una lista de números. Imprimir la suma de los
números.
toList :: String -> [Integer]
= read ("[" ++ input ++ "]")
toList input
= do
main putStrLn "Enter a list of numbers (separated by comma):"
<- getLine
input print $ sum (toList input)
Debería ser sencillo comprender el comportamiento de este programa. Analicemos los tipos en más detalle.
putStrLn :: String -> IO ()
getLine :: IO String
print :: Show a => a -> IO ()
O más interesante, notamos que cada expresión en el bloque do
tiene
el tipo IO a
= do
main putStrLn "Enter ... " :: IO ()
getLine :: IO String
print Something :: IO ()
También debemos prestar atención a los efectos del símbolo <-
.
do
<- something x
Si something :: IO a
entonces x :: a
.
Otra cosa importante a notar sobre usar IO
: Todas las lineas en un
bloque do
deben ser de una de dos posibles formas:
action1 :: IO a
-- in this case, generally a = ()
O
<- action2 -- where
value -- action2 :: IO b
-- value :: b
Estos dos tipos de linea corresponderán a dos formas diferentes de secuenciar acciones. El significado de esta sentencia debería quedar clara para el final de la siguiente sección.
Ahora veamos como se comporta el programa. Por ejemplo, qué ocurre si el usuario ingresa algo extraño? Intentemos:
% runghc 02_progressive_io_example.lhs
Enter a list of numbers (separated by comma):
foo
Prelude.read: no parse
Un horrible mensaje de error y un fallo! Nuestra primera mejora será responder con un mensaje más amigable.
Para hacerlo, debemos detectar que algo salió mal. Aquí hay una forma de
hacerlo: usar el tipo Maybe
. Este es un tipo muy común en Haskell.
import Data.Maybe
¿Qué es esto? Maybe
es un tipo que toma un parámetro. Su definición es:
data Maybe a = Nothing | Just a
Esta es una forma muy agradable de decir que hubo un error mientras se
intentaba crear/computar un valor. La función maybeRead
es un gran ejemplo de
esto. Esta es una función similar a la función read
[^4], pero si algo sale mal
el valor retornado es Nothing
. Si el valor es correcto, retorna Just <el valor>
. No intentes comprender mucho de esta función. Se usa una función de
nivel menor a read
; reads
.
maybeRead :: Read a => String -> Maybe a
= case reads s of
maybeRead s "")] -> Just x
[(x,-> Nothing _
Ahora para estar un poco más seguros, definimos una función que va así: Si la
cadena tiene el formato incorrecto, se retorna Nothing
. Caso contrario, por
ejemplo “1,2,3”, se retorna Just [1,2,3]
.
getListFromString :: String -> Maybe [Integer]
= maybeRead $ "[" ++ str ++ "]" getListFromString str
Simplemente tenemos que probar el valor en nuestra función principal main
.
main :: IO ()
= do
main putStrLn "Enter a list of numbers (separated by comma):"
<- getLine
input let maybeList = getListFromString input in
case maybeList of
Just l -> print (sum l)
Nothing -> error "Bad format. Good Bye."
En caso de error, mostramos un mensaje de error amigable.
Nótese que el tipo de cada expresión en el bloque do
de main permanece en
la forma IO a
. La única construcción extraña es error
. Aquí solo diré que
error msg
toma el tipo necesario (aquí IO ()
).
Una cosa importante es el tipo de todas las funciones definidas hasta ahora.
Solo hay una función que contiene IO
en su tipo: main
. Esto significa que
main es impura. Pero main usa getListFromString
que es pura. Entonces
queda claro solo observando los tipos declarados que funciones son
puras y cuales son impuras.
¿Por qué importa la pureza? Entre las muchas ventajas, aquí hay tres:
Por esto se debe poner todo el código posible dentro de funciones puras.
Nuestra siguiente iteración será pedir al usuario una y otra vez hasta que introduzca una respuesta valida.
Mantenemos la primera parte:
import Data.Maybe
maybeRead :: Read a => String -> Maybe a
= case reads s of
maybeRead s "")] -> Just x
[(x,-> Nothing
_ getListFromString :: String -> Maybe [Integer]
= maybeRead $ "[" ++ str ++ "]" getListFromString str
Ahora creamos una función que pregunte al usuario la lista de enteros hasta que la entrada sea correcta.
askUser :: IO [Integer]
= do
askUser putStrLn "Enter a list of numbers (separated by comma):"
<- getLine
input let maybeList = getListFromString input in
case maybeList of
Just l -> return l
Nothing -> askUser
Esta función es de tipo IO [Integer]
. Este tipo significa que obtendremos
un valor de tipo [Integer]
a través de acciones de entrada/salida (IO).
Algunas personas podrían explicar mientras sacuden las manos:
Esto es un
[Integer]
dentro de unIO
Si quieres comprender los detalles detrás de todo esto, tendrás que leer la siguiente sección. Pero en realidad, si solamente quieres saber como usar IO solo practica un poco y recuerda pensar sobre el tipo.
Finalmente la función main es mucho más simple:
main :: IO ()
= do
main <- askUser
list print $ sum list
Hemos terminado con la introducción a la entrada/salida IO
. Fue rápido.
Estas son las principales cosas que hay que recordar.
En el bloque do
, cada expresión debe tener el tipo IO a
. Así que estas
limitado en el numero de expresiones disponibles. Por ejemplo, getLine
,
print
, putStrLn
, etc…
Intenta independizar las funciones puras todo lo posible.
El tipo IO a
significa: una acción IO que retorna un elemento de
tipo a
. IO
representa acciones; por dentro, IO a
es el tipo de
una función. Lee la siguiente sección si te da curiosidad.
Si practicas un poco, deberías ser capaz de usar IO
.
Ejercicios:
* Hacer un programa que sume todos sus argumentos.
Pista: Usa la función getArgs
.
TL;DR*:
Para separar las partes puras de las impuras, main
es la función que
modifica el estado del mundo exterior
main :: World -> World
Una función garantiza que solo tendrá efectos secundarios si tiene este tipo.
Pero observa una función main
típica:
=
main w0 let (v1,w1) = action1 w0 in
let (v2,w2) = action2 v1 w1 in
let (v3,w3) = action3 v2 w2 in
action4 v3 w3
Tenemos varios elementos temporales (w1
, w2
, w3
) que deben ser pasados a
la siguiente sección.
Creamos una función bind
o (>>=)
. Con bind
ya no necesitamos nombres
temporales.
=
main >>= action2 >>= action3 >>= action4 action1
Bonus: Haskell tiene azúcar sintáctica para nosotros:
= do
main <- action1
v1 <- action2 v1
v2 <- action3 v2
v3 action4 v3
Por qué usamos esta sintaxis extraña, y qué es exactamente este tipo IO
?
Parece algo mágico.
Por ahora vamos a olvidarnos sobre las partes puras de nuestro programa, y enfocarnos en las partes impuras.
askUser :: IO [Integer]
= do
askUser putStrLn "Enter a list of numbers (separated by commas):"
<- getLine
input let maybeList = getListFromString input in
case maybeList of
Just l -> return l
Nothing -> askUser
main :: IO ()
= do
main <- askUser
list print $ sum list
Primera cuestión remarcarle: esto parece un programa imperativo. Haskell es
lo suficientemente poderoso para hacer código impuro lucir imperativo.
Por ejemplo, si deseas podrías crear un while
en Haskell. De hecho, para
lidiar con entrada/salida, un estilo imperativo es generalmente más apropiado.
Pero deberías haber notado que esta notación es un poco inusual. Aquí el por qué, en detalle.
En un lenguaje imperativo el estado del mundo puede ser visto como una gran variable global oculta. Esta variable oculta es accesible por todas las funciones del lenguaje. Por ejemplo, se pude leer desde un fichero en cualquier función. Sea que el fichero exista o no es una diferencia en el posible estado que el mundo puede tomar.
En Haskell este estado no es oculto. Más bien, se dice
explicitamemten que main
es una función que puede potencialmente
cambiar el estado del mundo. Su tipo es como:
main :: World -> World
No todas las funciones pueden tener acceso a esta variable. Aquellas que tienen acceso son impuras. Funciones a las que no se les provee esta variable del mundo son puras[^5].
Haskell considera el estado del mundo exterior como una variable de enterada a
main
. Pero el tipo real de main es más parecido a[^6]:
main :: World -> ((),World)
El tipo ()
es el tipo unitario. Nada que ver aquí.
Ahora escribamos nuestra función principal con esto en mente:
=
main w0 let (list,w1) = askUser w0 in
let (x,w2) = print (sum list,w1) in
x
Primero, notamos que todas las funciones que tienen efectos secundarios deben tener el tipo:
World -> (a,World)
Donde a
es el tipo del resultado. Por ejemplo, una función getChar
debería tener el tipo World -> (Char, World)
.
Otra cosa a notar es el truco para fijar el orden de la evaluación. En
Haskell, para evaluar f a b
, tienes varias opciones:
a
luego b
luego f a b
b
luego a
luego f a b
a
y b
en paralelo y luego f a b
Esto es verdad por que estamos trabajando en la parte pura del lenguaje.
Ahora, si tomas la función main
, está claro que debes evaluar la primera
linea antes de la segunda puesto que para evaluar la segunda linea es
necesario obtener el parámetro dado en la evaluación de la primera linea.
Este truco funciona bien. El compilador proveerá en cada paso un puntero a
un nuevo identificador del mundo real. Por dentro, print
se evaluará
como:
((),new world id)
.Ahora, si miras el estilo de la función main, es claramente incómodo. Intentemos hacer lo mismo a la función askUser:
askUser :: World -> ([Integer],World)
Antes:
askUser :: IO [Integer]
= do
askUser putStrLn "Enter a list of numbers:"
<- getLine
input let maybeList = getListFromString input in
case maybeList of
Just l -> return l
Nothing -> askUser
Después:
=
askUser w0 let (_,w1) = putStrLn "Enter a list of numbers:" in
let (input,w2) = getLine w1 in
let (l,w3) = case getListFromString input of
Just l -> (l,w2)
Nothing -> askUser w2
in
(l,w3)
Esto es similar, pero incómodo. Mira todos esos nombres temporales w?
.
La lección es: poner implementación IO
en lenguajes funcionales puros es
incómodo!
Afortunadamente, hay una mejor forma de manejar este problema. Observamos un patrón. Cada linea es de la forma:
let (y, w') = action x w in
Incluso si para alguna linea el primer argumento x
no es necesario.
La salida es de tipo tupla, (answer, newWorldValue)
. Cada función f
debe
tener un tipo similar a:
f :: World -> (a,World)
No solo eso, también podemos notar que siempre seguimos el mismo patrón de uso:
let (y,w1) = action1 w0 in
let (z,w2) = action2 w1 in
let (t,w3) = action3 w2 in
...
Cada acción puede tomar de 0 a n parámetros. Y en particular, cada acción puede tomar un parámetro del resultado de la linea anterior.
Por ejemplo, también podríamos tener:
let (_,w1) = action1 x w0 in
let (z,w2) = action2 w1 in
let (_,w3) = action3 x z w2 in
...
Y por supuesto actionN w :: (World) -> (a,World)
.
IMPORTANTE: Hay dos patrones importantes a considerar:
let (x,w1) = action1 w0 in
let (y,w2) = action2 x w1 in
Y
let (_,w1) = action1 w0 in
let (y,w2) = action2 w1 in
Ahora, haremos un truco de magia. Haremos que el símbolo del mundo temporal
“desaparezca”. Haremos un bind
a las dos lineas. Dinamos la función
bind
. Su tipo es un poco intimidarte al principio:
bind :: (World -> (a,World))
-> (a -> (World -> (b,World)))
-> (World -> (b,World))
Pero recuerda que (World -> (a,World))
es un tipo para una acción IO
. Ahora
renombremoslo por claridad:
type IO a = World -> (a, World)
Algunos ejemplos de funciones:
getLine :: IO String
print :: Show a => a -> IO ()
getLine
es una acción IO
que toma el mundo exterior como parámetro
y retorna una tupla (String,World)
. Esto se puede resumir como: getLine
es de tipo IO String
, que también vemos como una acción IO que retornará
una cadena “embeded inside an IO”.
La función print
también es interesante. Toma un argumento que puede ser
mostrado. De hecho puede tomar dos argumentos. El primero es el valor a
imprimir y el otro es el estado del mundo exterior. Luego retorna una tupla
de tipo ((),World)
. Esto significa que cambia el estado del mundo exterior,
pero no produce más información.
Este tipo nos ayuda a simplificar el tipo de bind
:
bind :: IO a
-> (a -> IO b)
-> IO b
Dice que bind
toma dos acciones IO como parámetros y retorna otra acción IO.
Ahora, recuerda los patrones importantes. El primero era:
let (x,w1) = action1 w0 in
let (y,w2) = action2 x w1 in
(y,w2)
Observa los tipos:
action1 :: IO a
action2 :: a -> IO b
:: IO b (y,w2)
Resulta familiar?
=
(bind action1 action2) w0 let (x, w1) = action1 w0
= action2 x w1
(y, w2) in (y, w2)
La idea es esconder el argumento del mundo exterior con esta función. Hagamoslo: Como un ejemplo imagina que queremos simular:
let (line1,w1) = getLine w0 in
let ((),w2) = print line1 in
((),w2)
Ahora, usando la función bind:
= (bind getLine (\l -> print l)) w0 (res,w2)
Como print es de tipo (World -> ((),World))
, sabemos que res = ()
(tipo
nulo). Si no te diste cuenta de la magia aquí, intentemos con tres lineas esta
vez:
let (line1,w1) = getLine w0 in
let (line2,w2) = getLine w1 in
let ((),w3) = print (line1 ++ line2) in
((),w3)
Que es equivalente a:
= (bind getLine (\line1 ->
(res,w3) getLine (\line2 ->
(bind print (line1 ++ line2))))) w0
Notaste algo? Si, nada de variables temporales del mundo exterior en ninguna parte! Esto es MÁGICO.
Podemos usar una mejor notación. Usemos (>>=)
en lugar de bind
. (>>=)
es
una función infijo como (+)
; Recuerda 3 + 4 ⇔ (+) 3 4
= (getLine >>=
(res,w3) -> getLine >>=
(\line1 -> print (line1 ++ line2)))) w0 (\line2
Haskell tiene azúcar sintáctica para nosotros:
do
<- action1
x <- action2
y <- action3
z ...
Se reemplaza con:
>>= (\x ->
action1 >>= (\y ->
action2 >>= (\z ->
action3 ...
)))
Nota que se puede usar x
en action2
y x
y y
en action3
.
Pero qué pasa con las lineas que no usan <-
?
Fácil, otra función blindBind
:
blindBind :: IO a -> IO b -> IO b
=
blindBind action1 action2 w0 -> action2) w0 bind action (\_
No simplifiqué esta definición por propósitos de claridad. Pero claro que
podemos usar una mejor notación, usaremos el operador (>>)
.
Y
do
action1
action2 action3
Se transforma en
>>
action1 >>
action2 action3
También, otra función útil.
putInIO :: a -> IO a
= IO (\w -> (x,w)) putInIO x
Esto es en general la forma de poner variables dentro de un “contexto
de IO”. El nombre general para ponerEnIO
es return
. Que es un mal nombre
cuando aprendes Haskell. return
es muy distinto de lo que puedes estar
acostumbrado.
Para finalizar, traduzcamos nuestro ejemplo:
askUser :: IO [Integer]
= do
askUser putStrLn "Enter a list of numbers (separated by commas):"
<- getLine
input let maybeList = getListFromString input in
case maybeList of
Just l -> return l
Nothing -> askUser
main :: IO ()
= do
main <- askUser
list print $ sum list
Se traduce a:
import Data.Maybe
maybeRead :: Read a => String -> Maybe a
= case reads s of
maybeRead s "")] -> Just x
[(x,-> Nothing
_ getListFromString :: String -> Maybe [Integer]
= maybeRead $ "[" ++ str ++ "]"
getListFromString str askUser :: IO [Integer]
=
askUser putStrLn "Enter a list of numbers (sep. by commas):" >>
getLine >>= \input ->
let maybeList = getListFromString input in
case maybeList of
Just l -> return l
Nothing -> askUser
main :: IO () main = askUser >>= -> print $ sum list
Puedes compilar este código y verificar que funciona.
Imagina como se vería sin el (>>)
y (>>=)
.
Ahora el secreto puede ser revelado: IO
es un monad.
Ser un monad significa que se tiene acceso a azúcar sintáctica con la
notación do
. Pero principalmente, se tiene acceso al patrón que facilitará
el flujo del código.
Aclaraciones importantes:
* Los monads no tratan necesariamente efectos secundarios!
Hay varios Monads puros.
* Los monads se tratan se secuenciar.
En Haskell, Monad
es una clase de tipo. Para crear una instancia de esta
clase de tipo, se deben proveer las funciones (>>=)
y return
. La función
(>>)
se deriva de (>>=)
. Aquí se muestra como la clase de tipo Monad
está declarada (básicamente):
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
(>>) :: m a -> m b -> m b
>> g = f >>= \_ -> g
f
-- You should generally safely ignore this function
-- which I believe exists for historical reasons
fail :: String -> m a
fail = error
Aclaraciones:
* La palabra `class` no es tu amiga. En Haskell *class* no es una clase del
tipo que encontraras en lenguajes orientados a objetos. En Haskell una clase
tiene más bien similitudes con las interfaces de Java. Una mejor palabra
hubiera sido `typeclass`, pues eso significa un conjunto de tipos. Para que un
tipo pertenezca a una clase, todas las funciones de la clase debe ser
proporcionadas por el tipo.
* En este ejemplo en particular de clase de tipo, el tipo `m` debe ser un tipo
que tome un argumento. Por ejemplo `IO a`, pero también `Maybe a`, `[a]`,
etc...
* Para que un monad sea útil, la función debe obedecer algunas reglas. Si
tu construcción no las obedece cosas extrañas pueden ocurrir:
* Return a >>= k == K a m >>= return == m m >>= (-> k x >>= h) == (m >>= k) >>=
h ~
Hay varios tipos diferentes que son instancias de Monad
. Una de las más
fáciles de describir es Maybe
. Si se tiene una secuencia de valores Maybe
,
se pueden usar monads para manipularlos. Es particularmente útil para remover
construcciones if..then..else..
profundas
Imagina una operación bancaria compleja. Eres candidato a ganar 700$ solo si puedes seguir una lista de operaciones sin que tu cuenta caiga hasta cero.
= account + value
deposit value account = account - value
withdraw value account
eligible :: (Num a,Ord a) => a -> Bool
=
eligible account let account1 = deposit 100 account in
if (account1 < 0)
then False
else
let account2 = withdraw 200 account1 in
if (account2 < 0)
then False
else
let account3 = deposit 100 account2 in
if (account3 < 0)
then False
else
let account4 = withdraw 300 account3 in
if (account4 < 0)
then False
else
let account5 = deposit 1000 account4 in
if (account5 < 0)
then False
else
True
= do
main print $ eligible 300 -- True
print $ eligible 299 -- False
Ahora, mejoremos esto usando Maybe
y el hecho de que es un Monad
deposit :: (Num a) => a -> a -> Maybe a
= Just (account + value)
deposit value account
withdraw :: (Num a,Ord a) => a -> a -> Maybe a
= if (account < value)
withdraw value account then Nothing
else Just (account - value)
eligible :: (Num a, Ord a) => a -> Maybe Bool
= do
eligible account <- deposit 100 account
account1 <- withdraw 200 account1
account2 <- deposit 100 account2
account3 <- withdraw 300 account3
account4 <- deposit 1000 account4
account5 Just True
= do
main print $ eligible 300 -- Just True
print $ eligible 299 -- Nothing
No esta nada mal, pero podemos mejorarlo más:
deposit :: (Num a) => a -> a -> Maybe a
= Just (account + value)
deposit value account
withdraw :: (Num a,Ord a) => a -> a -> Maybe a
= if (account < value)
withdraw value account then Nothing
else Just (account - value)
eligible :: (Num a, Ord a) => a -> Maybe Bool
=
eligible account 100 account >>=
deposit 200 >>=
withdraw 100 >>=
deposit 300 >>=
withdraw 1000 >>
deposit return True
= do
main print $ eligible 300 -- Just True
print $ eligible 299 -- Nothing
Hemos demostrado que los Monads son una buena forma de hacer el código más
elegante. Esta idea para organizar el código, en particular para Maybe
se
puede usar en la mayoría de los lenguajes imperativos. De hecho, este es más
o menos el tipo de construcciones que hacemos naturalmente.
Una aclaración importante:
El primer elemento en la secuencia evaluada a `Nothing` detendrá por
completo la evaluación. Esto significa que no se ejecutan todas las
lineas. Obtienes esto gratuitamente, gracias a la pereza (laziness).
También se puede replicar este ejemplo con la definición de (>>=)
para
Maybe
en mente:
instance Monad Maybe where
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= _ = Nothing
Just x) >>= f = f x
(
return x = Just x
El monad Maybe
probó ser útil en este ejemplo. Vimos la utilidad de el monad
IO
. Pero vamos por un mejor ejemplo, las listas.
El monad lista ayuda a simular cómputos no determinístico:
import Control.Monad (guard)
= [1..10]
allCases
resolve :: [(Int,Int,Int)]
= do
resolve <- allCases
x <- allCases
y <- allCases
z $ 4*x + 2*y < z
guard return (x,y,z)
= do
main print resolve
MAGIA:
[(1,1,7),(1,1,8),(1,1,9),(1,1,10),(1,2,9),(1,2,10)]
Para el monad lista, también hay azúcar sintáctica:
print $ [ (x,y,z) | x <- allCases,
<- allCases,
y <- allCases,
z 4*x + 2*y < z ]
No listaré todos los monads, pero hay muchos de ellos. Usar monads simplifica la manipulación de varias nociones en lenguajes puros. En particular, los monads son muy útiles para:
Si me has seguido hasta aquí, entonces lo lograste! Sabes Monads[^7]!
Esta sección no se trata de aprender Haskell. Solo está aquí para discutir a más profundidad algunos detalles.
En la sección Estructuras infinitas vimos algunas construcciones simples. Desafortunadamente removimos dos propiedades de nuestro árbol:
En esta sección intentaremos mantener la primera propiedad. Respecto a la segunda, debemos relajarla pero discutiremos como mantenerla todo lo posible.
El primer paso es crear una lista de números pseudo aleatorios:
= map (\x -> (x*3123) `mod` 4331) [1..] shuffle
Solo como recordatorio, aquí esta la definición de treeFromList
treeFromList :: (Ord a) => [a] -> BinTree a
= Empty
treeFromList [] :xs) = Node x (treeFromList (filter (<x) xs))
treeFromList (xfilter (>x) xs)) (treeFromList (
y treeTakeDepth
:
Empty = Empty
treeTakeDepth _ 0 _ = Empty
treeTakeDepth Node x left right) = let
treeTakeDepth n (= treeTakeDepth (n-1) left
nl = treeTakeDepth (n-1) right
nr in
Node x nl nr
Observa el resultado de:
= do
main putStrLn "take 10 shuffle"
print $ take 10 shuffle
putStrLn "\ntreeTakeDepth 4 (treeFromList shuffle)"
print $ treeTakeDepth 4 (treeFromList shuffle)
% runghc 02_Hard_Part/41_Infinites_Structures.lhs
take 10 shuffle
[3123,1915,707,3830,2622,1414,206,3329,2121,913]
treeTakeDepth 4 (treeFromList shuffle)
< 3123
: |--1915
: | |--707
: | | |--206
: | | `--1414
: | `--2622
: | |--2121
: | `--2828
: `--3830
: |--3329
: | |--3240
: | `--3535
: `--4036
: |--3947
: `--4242
Bien! Termina! Pero cuidado, solo funcionará si tiene algo que poner en la rama.
Por ejemplo
4 (treeFromList [1..]) treeTakeDepth
No terminará nunca. Por que intentará acceder a la cabeza de filter (<1) [2..]
. Pero filger
no es lo bastante inteligente para entender que el
resultado es una lista vacía.
Aun así, es un ejemplo muy bueno de lo que los programas no estrictos pueden ofrecer.
Como ejercicio para el lector:
n
tal que treeTakeDepth n (treeFromList shuffle)
entrará en un loop infinito.n
.suffle
tal que para cualquier
profundidad, el programa termina.Para resolver este problema modificaremos un poco las funciones
treeFromList
y shuffle
.
Un primer problema es la falta de infinitos números diferentes en nuestra
implementación de shuffle
. Solo hemos generado 4331
números distintos.
Para solucionarlo haremos una función shuffle
mejorada.
= map rand [1..]
shuffle where
= ((p x) `mod` (x+c)) - ((x+c) `div` 2)
rand x = m*x^2 + n*x + o -- some polynome
p x = 3123
m = 31
n = 7641
o = 1237 c
Esta función tiene la propiedad de no tener un limite superior o inferior. Pero tener una lista mejor mezclada no es suficiente para no entrar en un bucle infinito.
Generalmente, no podemos decidir si filter (<x) xs
está vacía. Entonces
para resolver este problema, autorizaré algo de error en la creación del árbol
binario. Esta nueva versión puede crear un árbol binario que no tienen la
siguiente propiedad para algunos de sus nodos:
Cualquier elemento en la rama izquierda debe ser estrictamente
inferior a la etiqueta de la raíz.
Permanecerá en su mayor parte un árbol binario ordenado. Más aún, por construcción, el valor de cada nodo es único en el árbol.
Aquí nuestra nueva versión de treeFromList
. Simplemente sea ha remplazado
filter
por safefilter
.
treeFromList :: (Ord a, Show a) => [a] -> BinTree a
= Empty
treeFromList [] :xs) = Node x left right
treeFromList (xwhere
= treeFromList $ safefilter (<x) xs
left = treeFromList $ safefilter (>x) xs right
Esta nueva función safefilter
es casi equivalente a filter
pero no entra en
un bucle infinito si el resultado es una lista infinita. Si no puede encontrar
un elemento para el cual la prueba resulte cierta luego de 10000 pasos
consecutivos, entonces considera que es el final de la búsqueda.
safefilter :: (a -> Bool) -> [a] -> [a]
= safefilter' f l nbTry
safefilter f l where
= 10000
nbTry 0 = []
safefilter' _ _ = []
safefilter' _ [] _ :xs) n =
safefilter' f (xif f x
then x : safefilter' f xs nbTry
else safefilter' f xs (n-1)
Ahora el programa se ejecuta bien:
= do
main putStrLn "take 10 shuffle"
print $ take 10 shuffle
putStrLn "\ntreeTakeDepth 8 (treeFromList shuffle)"
print $ treeTakeDepth 8 (treeFromList $ shuffle)
Se debería ver que el tiempo para imprimir cada valor es diferente. Esto es por que Haskell calcula cada valor cuando lo necesita. Y en este caso, esto ocurre cuando se solicita imprimirlo en pantalla.
Intenta remplazar la profundidad de 8
a 100
. Funcionará sin comerse tu
RAM! El flujo en el manejo de memoria es hecho de forma natural por Haskell.
Como ejercicio para el lector:
Incluso con un valor grande constante para deep
y nbTry
, parece funcionar
bien. Pero en el peor caso, puede ser exponencial. Crear una lista para el
peor caso y darlo como parámetro a treeFromList
. Pista: piensa en
[0,-1,-1,...,-1,1,-1,...,-1,1,...]
.
Primero intenté implementar safefilter
como:
= if filter f (take 10000 l) == []
safefilter' f l then []
else filter f l
Explica por que no funciona y puede entrar en un loop infinito.
shuffle
es una lista aleatoria real con limites crecientes.
Si estudias un poco esta estructura, descubrirás que con una
probabilidad de 1, esta es una estructura infinita. Usando el siguiente
código encuentra una definición de f
tal que con probabilidad de 1
,
treeFromList' shuffle
es infinita. Y pruebalo. (esto solo es una
conjetura).= Empty
treeFromList' [] n :xs) n = Node x left right
treeFromList' (xwhere
= treeFromList' (safefilter' (<x) xs (f n)
left = treeFromList' (safefilter' (>x) xs (f n)
right = ??? f
[^1]. Incluso si los lenguajes mas recientes intentan ocultarlos, están presentes.
[^2]. Se que estoy haciendo trampa. Pero hablaré “no estricto” luego.
[^3]. Para los valientes, una explicación más completa del patrón de matching se puede encontrar aquí.
[^4]. Es muy similar al eval
de javascript en una cadena que contiene
JSON.
[^5]. Hay algunas excepciones no seguras ha esta regla. Pero no deberías ver su uso en una aplicación real excepto tal vez para propósitos de depuración.
[^6]. Para los curiosos el tipo real es data IO a = IO {unIO :: State# RealWorld -> (#State# RealWorld, a #)}
. Los ‘#’ tienen que ver con la
optimización.
[^7]. Ciertamente necesitas practicar un poco para acostumbrarte a ellos y entender cuando los puedes usar y crearlos tu mismo. Pero ya haz hecho un gran avance.
]]>Having the ability to traverse Vim and Tmux splits without having to think about
it using ctrl-h
, ctrl-j
, ctrl-k
, ctrl-l
is brilliant! But I still had an
annoyance source from the window manager (Ratpoison) and the multi monitor
setup.
So I took the same concept and extend it to those uses cases. Now I use
ctrl-h
, ctrl-j
, ctrl-k
, ctrl-l
to move through my Window Manager
splits, my Tmux panes, my Vim windows and my Monitors with minimum
mental overhead. Here is how.
Some scripts are a bit of complex, so instead of explaining them in detail, the general algorithm is described.
When traversing frames (Ratpoison splits) it stops at the end of the current monitor, so first I needed to change to the left or right monitor when a movement command is triggered at the edge of the current one.
The script frame-mon_navigator.sh calculates if the current frame is the rightmost or the leftmost in the current monitor, if it is, it goes to the next of previous monitor depending on the movement command.
#!/bin/sh
ratpoison -c 'fdump'| sed 's/,/\n/g' | awk '{print $5" "$19}' > /tmp/ratpoison_frame_monitor_navigator
# Calculate X coordinate for rightmost frame
greater_x_coordinate=0
while read frame; do
coordinate=$(echo "$frame" | cut -d' ' -f1)
if [[ $coordinate -gt $greater_x_coordinate ]];
then
greater_x_coordinate=$coordinate
fi
done < /tmp/ratpoison_frame_monitor_navigator
# Calculate current frame X coordinate
x_coordinate=$(head -n1 /tmp/ratpoison_frame_monitor_navigator | cut -d' ' -f1)
last_access=$(head -n1 /tmp/ratpoison_frame_monitor_navigator | cut -d' ' -f2)
while read frame; do
access=$(echo "$frame" | cut -d' ' -f2)
if [[ $access -gt $last_access ]];
then
last_access=$access
x_coordinate=$(echo "$frame" | cut -d' ' -f1)
fi
done < /tmp/ratpoison_frame_monitor_navigator
function is_leftmost
{
if [[ $x_coordinate -eq 0 ]]; then
return 0
else
return 1
fi
}
function is_rightmost
{
if [[ $x_coordinate -eq $greater_x_coordinate ]]; then
return 0
else
return 1
fi
}
# Go to previous screen if currently in leftmost frame
# Go to next screen if currently in rightmost frame
# Execute frame focus otherwise
if [[ "$1" == "left" ]]; then
if is_leftmost; then
ratpoison -c 'prevscreen'
else
ratpoison -c 'focusleft'
fi
elif [[ "$1" == "right" ]]; then
if is_rightmost; then
ratpoison -c 'nextscreen'
else
ratpoison -c 'focusright'
fi
fi
We need a way to pass movement commands to Tmux so Vim-Tmux navigation works as always, but we also need to pass movement commands from Tmux to Ratpoison when a movement command is triggered from Tmux edge pane.
The script rat_tmux-navigator.sh is able to tell if the terminal emulator (Urxvt) is currently focused and, if so, send the movement commands to Tmux, so it can handle panes traversing as usual. It also defines functions that Tmux can use to know if an edge pane is reached and send the movement commands to Ratpoison through the frame-mon_navigator.sh script so Frame-Monitor navigation is included in the process.
#!/bin/bash
if [[ "$1" == "rat" ]]; then
current_window=$(ratpoison -c 'info' | sed 's/(.*).*(\(.*\))/\1/'\
| tr '[:upper:]' '[:lower:]')
elif [[ "$1" == "tmux" ]];then
window_bottom=$(tmux list-panes -F "#{window_height}" | head -n1)
window_right=$(tmux list-panes -F "#{window_width}" | head -n1)
window_bottom=$(($window_bottom - 1))
window_right=$(($window_right - 1))
pane=$(tmux list-panes -F "#{pane_left} #{pane_right} #{pane_top} #{pane_bottom} #{pane_active}" | grep '.* 1$')
pane_left=$(echo "$pane" | cut -d' ' -f 1)
pane_right=$(echo "$pane" | cut -d' ' -f 2)
pane_top=$(echo "$pane" | cut -d' ' -f 3)
pane_bottom=$(echo "$pane" | cut -d' ' -f 4)
fi
function rat_up
{
if [[ "$current_window" == "urxvt" ]];
then
ratpoison -c 'meta C-k'
else
ratpoison -c 'focusup'
fi
}
function rat_down
{
if [[ "$current_window" == "urxvt" ]];
then
ratpoison -c 'meta C-j'
else
ratpoison -c 'focusdown'
fi
}
function rat_right
{
if [[ "$current_window" == "urxvt" ]];
then
ratpoison -c 'meta C-l'
else
~/.scripts/ratpoison/frame-mon_navigator.sh right
fi
}
function rat_left
{
if [[ "$current_window" == "urxvt" ]];
then
ratpoison -c 'meta C-h'
else
~/.scripts/ratpoison/frame-mon_navigator.sh left
fi
}
function tmux_up
{
if [[ $pane_top -eq 0 ]];
then
ratpoison -c 'focusup'
else
tmux select-pane -U
fi
}
function tmux_down
{
if [[ $pane_bottom -eq $window_bottom ]];
then
ratpoison -c 'focusdown'
else
tmux select-pane -D
fi
}
function tmux_right
{
if [[ $pane_right -eq $window_right ]];
then
~/.scripts/ratpoison/frame-mon_navigator.sh right
else
tmux select-pane -R
fi
}
function tmux_left
{
if [[ $pane_left -eq 0 ]];
then
~/.scripts/ratpoison/frame-mon_navigator.sh left
else
tmux select-pane -L
fi
}
if [[ "$1" == "rat" ]];then
case "$2" in
'up')
rat_up
;;
'down')
rat_down
;;
'right')
rat_right
;;
'left')
rat_left
;;
esac
elif [[ "$1" == "tmux" ]];then
case "$2" in
'up')
tmux_up
;;
'down')
tmux_down
;;
'right')
tmux_right
;;
'left')
tmux_left
;;
esac
fi
Modifying Tmux mappings to use above scripts will make it work for
Tmux-Ratpoison traversing but when a Vim instance is on a Tmux edge pane it will
not jump to the appropriate Ratpoison split. To solve it I forked the
vim-tmux-navigator
project and made the right changes to it in the
vim-tmux-wm-monitor
branch
Then using vim-plug I install it in my
.vimrc
with:
Plug 'alx741/vim-tmux-navigator', { 'branch': 'vim-tmux-wm-monitor' }
Putting all together requires the appropriate mappings for Ratpoison and Tmux. Vim is already configured with the forked plugin.
These lines on .ratpoisonrc
will do the top level handling. Take into account
the path to the rat_tmux-navigator.sh
script.
definekey top C-k exec ~/.scripts/ratpoison/rat_tmux-navigator.sh rat up
definekey top C-j exec ~/.scripts/ratpoison/rat_tmux-navigator.sh rat down
definekey top C-l exec ~/.scripts/ratpoison/rat_tmux-navigator.sh rat right
definekey top C-h exec ~/.scripts/ratpoison/rat_tmux-navigator.sh rat left
Finally, these lines on .tmux.conf
are basically modified versions of the
vim-tmux-navigator
plugin ones.
is_vim="ps -o state= -o comm= -t '#{pane_tty}' \
| grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'"
bind-key C-h if-shell "$is_vim" "send-keys C-h" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux left'"
bind-key C-j if-shell "$is_vim" "send-keys C-j" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux down'"
bind-key C-k if-shell "$is_vim" "send-keys C-k" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux up'"
bind-key C-l if-shell "$is_vim" "send-keys C-l" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux right'"
bind-key -n C-h if-shell "$is_vim" "send-keys C-h" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux left'"
bind-key -n C-j if-shell "$is_vim" "send-keys C-j" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux down'"
bind-key -n C-k if-shell "$is_vim" "send-keys C-k" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux up'"
bind-key -n C-l if-shell "$is_vim" "send-keys C-l" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux right'"
bind-key h if-shell "$is_vim" "send-keys C-h" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux left'"
bind-key j if-shell "$is_vim" "send-keys C-j" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux down'"
bind-key k if-shell "$is_vim" "send-keys C-k" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux up'"
bind-key l if-shell "$is_vim" "send-keys C-l" "run '~/.scripts/ratpoison/rat_tmux-navigator.sh tmux right'"
]]>Here I’d like to point out some things I deem important about tooling.
This might be obvious to most C programmers, but I’ve seen quite a lot of people, novices specially, copying and pasting compilation commands on every iteration.
Using a build tool will help you automate the building process, but also testing, distribution package generation, etc.
In order to sanely write C code the bear minimum you need is to know and feel
comfortable writing and
using
makefiles, so the compilation process can be described as a recipe and
triggered by issuing the $ make
command.
Using make alone by writing makefiles will take you pretty far, but for larger software systems you might want to automate things even further: examine the target system for both static and dynamic libraries, binaries available and configure things to adapt to the system and be as portable as possible. Autotools to the rescue.
Learning and using Autotools is not much of a trivial task, but when the complexity in your code starts to get out of hand, Autotools do outweigh the effort of getting a grasp on it.
If your code needs not only be portable on Posix systems, but also get compiled on Windows machines, CMake is what you need.
You can’t get any better at writing C code if you’re not familiar enough with the Standard C library (libc). I’ve seen developers trying to re-invent error reporting for instance, so make sure to be familiar with libc’s error reporting mechanisms, as well as everything else it has to offer, you’ll be pleasantly surprised.
A linter, is a program that will statically check the source code (not the binaries) to find any known non-portable constructs, vulnerabilities from common programming mistakes, bad practices and any other general coding mistakes that can cause your program leak memory, step on segmentation faults and the like.
Splint is one such linter. It will tell you a lot about what your code might be doing wrong.
You can use it very easily by specifying the source files like:
$ splint foo.c bar.c
Most of splint’s output will be suggestions rather than critical warnings, but following its recommendations with poise will make your code more robust.
You can tune the level of paranoia with these arguments: -weak
, -standard
,
-cheks
and -strict
.
Valgrind is a profiling program with more than a few neat tricks up its sleeve. In contrast to splint, it will use your executable program to help you find memory leaks, make it faster and correct.
When compiling your program use the -g
compiler flag to include extra
debugging information in the executable.
Then you can run you program with Valgrind like this:
$ valgrind foobar arg1 arg2
That will use the Memcheck
tool, one of multiple Valgrind’s
tools.
Yeah sure, you can fill up you code with printf
calls for debugging and pretty
much get away with it, but you’re missing out on the power a proper debugger
brings to the table. Some debugging sessions will be far easier with
GDB than a bunch of printf
lines all
around.
You might think you can get away with keeping multiple directories for each version of your program if it’s small enough, but that mindset will eventually bite you. A control version system will give you some superpowers for collaboration, version restoring, multi-branching, proper history tracking, back up and so much more.
You could use CVS or SVN (Subversion), but should prefer more modern systems like Mercurial, Darcs or Git.
Furthermore, even if you’re working alone in a project and won’t collaborate with more developers, using a repository hosting service like Bitbucket, GitHub, or GitLab is a great way to always have a backup of your code. And in the future, if more people join to your project, collaboration will be frictionless.
Documentation is like sex: when it is good, it is very good; and when it is bad, it is better than nothing –Dick Brandon
Nobody likes to write and maintain documentation, so keep it as automated as possible.
Using tools like Doxygen will provide documentation generation from source code and multi-target format documentation (HTML, LATEX, PDF, TROFF Man pages, PostScript, etc).
Remember to use your abilities writing Make recipes to automate the documentation process as well!
Always write documentation in ways that every possible aspect of it can be automated. Don’t write documentation using MS Word!. Use Markdown, AsciiDoc, DocBook.
If you really want a WYSIWYG tool, LibreOffice has a CLI interface that allows you to generate PDF files, so you can add in your Make recipe something like:
document.pdf: document.odt
libreoffice --convert-to pdf $<
You can even automatize some graphics generation using DOT.
In a nutshell, unit testing is about writing pieces of code that will exercise the functions of your software and compare the results to what it is expected to produce. Think of it as writing a program tu use your program and automatically check if it does what it’s supposed to do.
You can take this approach further by practicing Test Driven Development (TDD).
Although you could write test functions by hand, there are some great testing
frameworks that will make things smoother. I like
Check in particular, running $ make check
will test your software.
Writing tests with Check is pretty simple, take a look:
#include <check.h>
#include "../src/foo.h" // Contains the 'add' function
(my_test)
START_TEST {
int result = add(2, 2);
(result, 4);
ck_assert_int_eq}
END_TEST
This test will use your add
function, declared in src/foo.h
, and assert
that the result of adding 2
and 2
equals 4
, so next time changes are made
in the add
function that make it misbehave, you’ll catch the bug when running
the tests. Granted this example is over simplistic, but you get the idea. Check
every possible edge case. The more robust the tests are, the more robust your
program will be.
Learning how to think functionally will improve your C code despite C being an imperative language, you’ll stop using mutable global state and all the kind of stuff that prevents your software from being multi thread safe, and correct in general.
If you work on embedded software, you’re probably writing in C. Considering that even relatively cheap embedded hardware today has more than one core, parallelism is pretty important and a functional programming mind set will help you to get it right.
There are many multi-paradigm languages out there, like Python and Ruby for instance, but my personal recommendation is: Learn a purely functional programming language, in particular, blow your mind with Haskell.
Eric Raymond said:
The more you can avoid programming in C the more productive you will be.
And he’s got a point. However, I don’t believe C is a language you should need to avoid, instead, do write in C when you can take advantage of its power and can afford the additional effort it takes to handle that power.
Depending on what you’re working on, other languages would probably fit better and give you higher level abstraction with just a small perforce hit. In most cases, when you think you need C you can probably write it in Rust or Go (I recommend the former) and get the work done with great performance and low level management only when needed.
C is not a monster you have to hide from, it’s just a (wonderful) tool. You have to pick the right tool for the job. C is the right tool for many jobs.
]]>TL;DR: I was previously a PIC user but decided I hate it, switched to AVR and love it!
PIC from Microchip and AVR from Atmel are both wonderful families of microcontrollers for the hobbyist and professional as well. I’m going to argue, however, that AVR is overall better for every purpose and because of multiple reasons.
I’m referring to programming/flashing hardware here. The only feasible way I currently have to program PIC uCs is by using my parallel port PIC programmer and the almost forgotten at this point, though amazing Odyssey software.
The only feasible way you say? Yes!, at the time of writing, getting a PICkit (2 or 3) requires at least a $100+ (USD) budget. Any other solution like PICkit clones are not much cheaper either. Not at all a reasonable budget for the 3rd world hobbyist.
Using a Microchip’s PICkit (or a clone) requires using the pk2cmd privative software, which means that doing anything outside MPLAB is a major PITA.
AVR on the other hand, lets you flash chips so easily and for so cheap!, A DAPA (or DASA) programmer is simple, inexpensive and fast. Both the USBTiny and the USBASP programmers are readily available at reasonable prices online and can be used with the avrdude CLI tool, a much welcomed improvement over MPLAB behemoth.
Yes, Microchip provides a complete, fully compatible IDE (MPLAB) that can run in Unix* systems and can talk to PICkit. But using an IDE pains me, and using privative software that only works with privative hardware pains me even more.
I want a Free Software (as in Freedom) command line tool to drive a reasonably priced programmer hardware. The Odyssey utility that I’ve mentioned is a blessing!, but getting (Free) software for a Serial programmer, a PICkit or a PICkit clone is impossible, nobody cares about PIC Free tooling, just go and use all the privative, restrictive stuff that Microchip forces onto you.
Avrdude solves everything. A unified (GPL) tool that can drive any programmer with any hardware interface. I absolutely love it!
The same problem here, Microchip provides a freeware (privative) compiler –that goes as far as to restrict some optimizations for the freeware user– and the only sane way to use it is through the bloated IDE.
The SDCC compiler solves this. Kind of… Look I really like SDCC, it’s an excellent Free Software compiler, but the PIC port is not that good (yet?), it still requires you to use non-free Microchip’s header files and linker mappings.
With AVR, you get to use the GCC port. Yes that’s right, the GNU freaking C compiler! And you also get a fully featured GPL avr-libc on top of that.
I’ve always struggled to find help with PIC. Sure there is a lot out there, Microchip’s official documentation is very good and professional, but even in Microchip’s own forums you’re not able to get the level of community help you can get from AVR’s community.
AVR has a hacker/hobbyist/professional Free Software and Open Hardware centered community that makes it so much better overall.
For me PIC is horrible mostly because I dislike IDE’s and prefer to use CLI tools that I can easily script with, adapt to powerful text editors, run on remote machines over network and so on. I acknowledge, however, that many developers feel the opposite way and dislike the command line interface and/or couldn’t live without an IDE, so the reasons I don’t like PIC and love AVR might be the same reasons why you love PIC instead.
It’s all about tooling. When I say “I don’t like PIC”, what I really mean is: “I don’t like PIC’s tooling”. Both PIC and AVR have extremely powerful and comparable hardware. I do like the devices from both of them.
]]>C-t f
:
**Command** **Action**
f Facebook
y Youtube
r Reddit
g Github
o Open a new tab
w Open a new window
s Search for the current content in the clipboard
/ Jump to the tab with url mathing a user input
l Open a new tab with the lyrics of the currenlty playing song (mpd)
This does a bit more than what you’re probably thinking. Take for instance the
f
command with the “Facebook” action, it will afford you this:
No matter where you are, which window has the focus, or even if Firefox is currently running or not. Firefox will be started (if needed) and acquire the focus, then all your tabs will be parsed (starting from the last one), and if a Facebook tab is found then jump to it, if there is no Facebook tab then start a new one.
The same is extended to any of the other sites available (The list can be extended to suit you needs).
The o
commands is self-explanatory, the only advantage of this one is the
ability to quickly get a new tab no matter where you are, which window has the
focus, or if Firefox is running or not.
The s
command is quite nice, imagine this:
You’re trying to compile some code, but the compiler complains with a cryptic
message, so you use tmux to copy the error
message,
then issue the key sequence C-t f /
and BANG!, you get a new Firefox tab in
front of you with the search engine results for the error message. And this is
applicable to any content in your clipboard as well!
The /
command prompts the user for a query and jumps to the tab with a URL
that contains the query as a substring.
The l
command will take the name of the currently playing song in MPD, search
for it, and open the first result for the song lyrics in a new tab.
So you’re sold, let’s make it happen. The main dependencies are:
You can install them all with the system package manager, except for Mozrepl which you can get from Firefox addons.
This also depends on a Ratpoison script introduced in a previous post, so make sure to get that first.
Some extra ~/.ratpoisonrc
is needed for the new mappings:
newkmap firefox
definekey firefox f exec ~/.scripts/ratpoison/firefox.sh select_tab facebook
definekey firefox y exec ~/.scripts/ratpoison/firefox.sh select_tab youtube
definekey firefox e exec ~/.scripts/ratpoison/firefox.sh select_tab evirtual
definekey firefox r exec ~/.scripts/ratpoison/firefox.sh select_tab reddit
definekey firefox g exec ~/.scripts/ratpoison/firefox.sh select_tab github
definekey firefox o exec ~/.scripts/ratpoison/firefox.sh new_tab
definekey firefox w exec ~/.scripts/ratpoison/firefox.sh new_window
definekey firefox s exec ~/.scripts/ratpoison/firefox.sh clipboard_search
definekey firefox l exec ~/.scripts/ratpoison/firefox.sh search_lyrics
definekey firefox slash exec ~/.scripts/ratpoison/firefox.sh search_tab
bind f readkey firefox
Most of the magic is performed by Mozrepl. Unfortunately, I couldn’t get it to load an external script, though Expect is needed for the communication with it anyways, so let’s use it to hand the script line by line.
The select_tab.js
script
is on charge of parsing the tabs to find one that matches the query and jump to
it.
function selectTab(page) {
var numTabs=gBrowser.browsers.length;
var url="";
for(i=numTabs-1; i>0; i--) {
=gBrowser.browsers[i].contentDocument.location.href;
urlif(url.search(page) != -1) {
.tabContainer.selectedIndex=i;
gBrowserreturn true;
}
}return false;
}
Use Expect and the select_tab.expect
script
to perform the telnet communication with Mozrepl and send the script and
commands as well.
#!/usr/bin/expect
set page [lindex $argv 0]
set port 4242
set file [open "select_tab.js"]
set content [split [read $file] "\n"]
close $file
spawn telnet localhost $port
foreach line $content {
send "$line\r"
}
send "selectTab(\"$page\");\r"
expect "repl2> "
expect {
"true" {
exit 0
}
"false" {
exit 1
}
}
Now the firefox.sh
script,
invoked from Ratpoison, will glue it all together.
#!/bin/bash
URL=""
function set_url {
case "$1" in
'facebook')
URL="www.facebook.com"
;;
'youtube')
URL="www.youtube.com"
;;
'reddit')
URL="www.reddit.com"
;;
'github')
URL="www.github.com"
;;
'evirtual')
URL="evirtual.ucuenca.edu.ec"
;;
esac
}
function select_tab {
cd ~/.mozrepl/
expect select_tab.expect "$1" > /dev/null
if [[ $? != 0 ]]; then
set_url "$1"
if [[ "$URL" != "" ]]; then
firefox --new-tab "$URL"
fi
fi
}
function search_tab {
query=`ratpoison -c "prompt [Tab] > "`
if [[ "$query" == "" ]]; then exit 0; fi
select_tab "$query"
}
function clipboard_search {
search=$(xclip -selection clipboard -o)
if [[ "$search" == "" ]]; then
exit 0
fi
search=$(echo "$search" | sed 's/ /+/g')
google_url="https://www.google.com/search?q=$search"
firefox --new-tab "$google_url"
}
function search_lyrics {
search=$(mpc | head -n 1)
if [[ "$search" == "" ]]; then
exit 0
fi
search+=" lyrics"
search=$(echo "$search" | sed 's/ /+/g')
curl -A 'Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0'\
"https://www.google.com/search?q=$search"\
> /tmp/google_search_result.html
url=$(sed 's/>/>\r\n/g' /tmp/google_search_result.html\
| grep -m 1 '<a href="http:.*".*>'\
| sed -e 's/.*href="\([^"]*\)".*/\1/')
firefox --new-tab "$url"
}
case $1 in
'select_tab')
~/.scripts/ratpoison/app_select.sh firefox
select_tab $2
;;
'search_tab')
search_tab
;;
'new_tab')
~/.scripts/ratpoison/app_select.sh firefox
firefox --new-tab "http://www.google.com"
;;
'new_window')
ratpoison -c "nextscreen"
firefox --new-window "http://www.google.com"
;;
'clipboard_search')
~/.scripts/ratpoison/app_select.sh firefox
clipboard_search
;;
'search_lyrics')
~/.scripts/ratpoison/app_select.sh firefox
search_lyrics
;;
esac
You can find all those scripts and configuration bits in my Dotfiles.
]]>