Вторая программа на Nim – скрипт скачивания картинок из альбома Vkontakte.
(2015) - Версия на ScalaСледующий эксперимент после решения задачи Джеймса Бонда-младшего - скрипт для выкачивания всех картинок из альбома вконтакте в несколько потоков.
import httpclient, strtabs, json, cgi, strutils, strformat | |
import locks, os | |
const | |
LOGIN = YOUR_LOGIN | |
PASS = YOU_PASSWORD | |
ApiUrl = "https://api.vk.com/method/" | |
ApiVer* = "5.85" | |
AuthScope = "all" | |
ClientId = "3140623" | |
ClientSecret = "VeWdmVclDCtn6ihuP1nt" | |
type | |
VkAuthError* = object of Defect | |
VkApiError* = object of Defect | |
var | |
threadPool: array[0..4, Thread[seq[string]]] | |
L: Lock | |
client : HttpClient | |
proc encode(params: StringTableRef): string = | |
var parts = newSeq[string]() | |
for key, val in params: | |
parts.add(encodeUrl(key) & "=" & encodeUrl(val)) | |
result = parts.join("&") | |
proc login(login, password: string) : string = | |
let authData = { | |
"client_id": ClientId, | |
"client_secret": ClientSecret, | |
"grant_type": "password", | |
"username": login, | |
"password": password, | |
"scope": AuthScope, | |
"v": ApiVer, | |
"2fa-supported": "1" | |
}.newStringTable() | |
let resp = client.post("https://oauth.vk.com/token", body = authData.encode()) | |
let answer = parseJson(resp.body) | |
if "error" in answer: | |
raise newException(VkAuthError, answer["error_description"].str) | |
else: | |
result = answer["access_token"].str | |
proc request(token: string, name: string, params = newStringTable()): JsonNode = | |
params["v"] = ApiVer | |
params["access_token"] = token | |
let data = parseJson client.postContent(ApiUrl & name, body = params.encode()) | |
let error = data.getOrDefault("error") | |
if not error.isNil(): | |
raise newException(VkApiError, $error) | |
result = data.getOrDefault("response") | |
if result.isNil(): | |
result = data | |
proc getPhotosUrls(data:JsonNode) : seq[string] = | |
for photoInfo in data["items"]: | |
let photoSizes = photoInfo["sizes"] | |
let url = photoSizes[photoSizes.len-1]["url"].getStr.replace("\"", "") | |
result.add(url) | |
client = newHttpClient() | |
let token = login(LOGIN, PASS) | |
let wall_photos = request(token, "photos.get", {"album_id":"wall"}.newStringTable) | |
let urls : seq[string] = getPhotosUrls(wall_photos) | |
var urlsIndex = urls.len - 1 | |
proc threadFunc(urls: seq[string]) {.thread.} = | |
let threadClient = newHttpClient() | |
while true: | |
acquire(L) | |
if urlsIndex < 0: | |
release(L) | |
return | |
let url = urls[urlsIndex] | |
let i = getThreadId() | |
echo fmt"Thread:{i} Index:{urlsIndex} Url:{url}" | |
threadClient.downloadFile(url, fmt"test/{urlsIndex}.jpg") | |
urlsIndex-=1 | |
release(L) | |
sleep(1) | |
initLock(L) | |
for i in 0..high(threadPool): | |
createThread(threadPool[i], threadFunc, urls) | |
joinThreads(threadPool) | |
deinitLock(L) |
Смысл выбора задачи - попробовать использовать Nim в качестве скриптового языка, для написания quick-and-dirty кода.
Код на Nim
, написанный в таком стиле, похож на Pascal
. Однако после изучения стиля нескольких библиотек становятся ощутимее различия. Разница в том, что импортируется из модулей в программу. Это не просто “структуры данных и алгоритмы”, а элементы синтаксиса. В Nim
, по сравнению с Python
, меньше синтаксического сахара в языке, но его можно добавить, импортируя “сахарные” библиотеки. Практически, каждый модуль может быть написан на своём “микро-языке”, в зависимости от того, что он импортирует.
Примеры библиотек:
Async/await
Pattern matching
HTML DSL
В терминах Фаулера из книги “Предметно-ориентированные языки программирования”, библиотеки могут представлять свободные API
(fluent interface, “языкообразные апи”) или API командных запросов
(“обычные” для объектного программирования вызовы методов у объектов), причем одна и та же библиотека может представлять различные API для работы с ней.
Гибкий синтаксис приближает Nim
к таким языкам, как Lisp
или Smalltalk
. Возможно, правильный выбор языков может сильно уменьшать количество строк кода, необходимого для решения задач, хотя пока о том, что даст языково-ориентированное программирование, больше мечтают (MPS, Language Workbenches, Racket).
Вдобавок к изменениям синтаксиса, компилятор имеет переключатели, существенно меняющие поведение (hot code reload, ARC garbage collector, threads) и использовать C++ в качестве “ассемблера” для сборки под разные платформы.